进程
定义:
- 进程是系统进行资源分配和调度的基本单位
- 是程序执行的实体
- 是线程的容器
- 不同拥有自己独立的内存空间
进程切换:
线程:
定义:
- 线程是OS能
调度
的最小单位 - 大部分情况下包含在进程中
- 同一线程中的进程共享同一资源:虚拟地址空间、文件描述符、信号处理等,但同一进程的多条线程有各自的调用栈、寄存器、线程本地存储。
- 线程间共享同进程的共享内存空间
线程切换过程:
线程池
- 线程过多导致额外开销(创建、销毁、调度),使用线程池来维护多进程、等待任务分配
- 一般是生产消费模型,由生产者(生成工作任务、存入队列)、消费者(取出任务进行处理,没有则挂起)、任务队列组成 。
- 线程池分为:核心线程(线程池启动时就创建,长期存在,无限期等待任务)、非核心线程(任务数超出核心线程负荷时创建,创建后没有新任务就会回收掉)
协程
为什么有了线程还需要协程
- 多线程开发设计变得更复杂、需要考虑多同步竞争问题、如锁、竞争冲突
- 多线程创建也需要大量内存,虚拟内存也需要大约4MB
定义:
- 用户视角的一种抽象,os没有协程概念
- 本质时在单线程内运行,在线程内进行切换
- 拥有自己的上下文和栈,协程调度切换时保留寄存器上下文和栈,切回来时恢复
- goroutine是golang中对协程的实现
- goroutine底层实现了少量线程干多事,减少切换时间等
协程切换:
goroutine
定义:
- 是Go语言的一种机制,GO语言协程中的实现
- 在
语言中
内置了调度和切换上下文机制,可将goroutine中任务合理分配给CPU
GPM
- 是Go自己实现的一套调度系统
- 全局队列存放着待运行的goroutine
- p:管理着一组Gorutine队列,容量有限,只能放
256
个,新建G时优先放入P队列,队列满了放入全局队列 - P 里面会存储当前G的上下文环境(函数指针、堆栈地址、地址边界),P会对自己管理的G队列进些调度,当自己队列消费完了,就去全局队列里取。如果全局队列也消费完了,就去其他P里抢任务。
代码练习
test1
/*
*
@author: tangjing
@date: 2023/6/4
@note:
*
主线程开启goroutine 每隔1S输出 "你好唐经"
主线程中每隔 2s 输出 go routine 十次后退出
主线程和 goroutine同时执行
*/
package main
import (
"fmt"
"time"
)
/*
*
协程 在main方法执行完后就结束
go 1.8之前要设置CPU核心数, runtime.GOMAXPROC(16),1.8后全开
查看核心数 runtime.NumCPU()
*/
func main() {
go runTimes(10) //直接加协程
for i := 0; i < 10; i++ {
fmt.Println("main", i, "剩余次数", 10-i)
time.Sleep(time.Second * 2)
}
}
func runTimes(times int) int {
for i := 0; i < times; i++ {
fmt.Println("runTimes", i, "剩余次数", times-i)
time.Sleep(time.Second)
}
return times
}
互斥锁解决资源竞争问题:
/*
*
@author: tangjing
@date: 2023/6/4
@note:
*
MAP 并发写:
直接运行:报错 fatal error: concurrent map writes
go build -race main.go && go build main.go检测数据竞争状态,再次执行会提示 WA
解决方案:
1、互斥锁
全局变量 通过加锁lock unlock 的方法达到线程安全
lock sync.Mutex
lock.lock()等使用完 lock.Unlock()
2、channel通道
*/
package main
import (
"fmt"
"sync"
"time"
)
var (
testMap = make(map[int]int, 10)
lock sync.Mutex
)
func testNum(num int) {
lock.Lock()
res := 1
for i := 1; i <= num; i++ {
res *= i
}
testMap[num] = res
lock.Unlock()
}
func main() {
start := time.Now()
for i := 1; i < 20; i++ {
go testNum(i)
}
//协程要在main之后完毕
time.Sleep(time.Second * 5)
//注意 这里也存在竞争关系,因为 在读取testMap 时,仍然有资源在写入testMap
for key, value := range testMap {
fmt.Printf("数字 %v ,对应的阶乘是 %v\n", key, value)
}
end := time.Since(start)
fmt.Println(end)
}
channel 的使用
在这里插入代码片/*
*
@author: tangjing
@date: 2023/6/5
@note:
channel 本质是队列 FIFO , 线程安全
attention: channel是有类型的, 。当然如果传空接口就能接受所有类型
定义/声明Channel : var intChan chan int
需要make之后才可以使用 intChan = make (chan int,6)
*
*/
package main
import "fmt"
var intChan = make(chan int, 10)
func main() {
intChan <- 1
intChan <- 2
fmt.Println(intChan) // 打印出来是指针地址
fmt.Printf("intChan的取出来第一个值是%v,地址是%v\n\n", <-intChan, &intChan)
fmt.Printf("intChan的大小-1是%v,容量是%v\n\n", len(intChan), cap(intChan))
<-intChan
//num := <-intChan
//如果队列被取空还在取 会产生死锁问题,当然 如果超出范围还在插也会死锁
//fmt.Printf("num = %v", num)
mapChan := make(chan map[int]string, 5)
map1 := make(map[int]string, 2)
map1[1] = "tangjing"
map1[2] = "jing"
mapChan <- map1
allChan := make(chan interface{}, 5)
allChan <- Dog{Name: "阿花", Color: "小黄"}
allChan <- 1
allChan <- 2
dog1 := <-allChan
fmt.Printf(" %T \n", dog1)
//fmt.Println("%T",dog1.Color) 直接运行报错,因为编译阶段识别出来的类型是interface{} 而不是Dog类型
//类型断言(Type Assertion)是一个使用在接口值上的操作,用于检查接口类型变量所持有的值是否实现了期望的接口或者具体的类型。value, ok := x.(T)
a, _ := dog1.(Dog)
fmt.Printf(" %T \n", a.Color)
}
type Dog struct {
Name string
Color string
}
/*
*
@author: tangjing
@date: 2023/6/5
@note:
channel的循环遍历与关闭
for range循环取值 需要close(chanName)
管道关闭后 不能写入
否则 fatal error: all goroutines are asleep - deadlock! 死锁
*
*/
package main
import "fmt"
func main() {
allChan := make(chan interface{}, 5)
allChan <- Dog1{Name: "阿花", Color: "小黄"}
allChan <- 1
allChan <- 2
//close(allChan) //如果不关闭就循环 则会存在问题
for val := range allChan {
fmt.Printf("%v\n", val)
}
}
type Dog1 struct {
Name string
Color string
}
思路: