image.png
G=goroutine:里面出坑了存储goroutine以外还存储了P的关联信息
P=PROCESS:管理一组G,P里面会存储goroutine运行的上下文环境(函数指针、堆栈地址及地址边界),P会对G做一些调度策略。P维护一个runq_用来存放等待执行的goroutine,新创建的G会优先放在P的本地队列,当本地队列满(256G)时,会放入G的全局队列
M=Thread=Machine(系统线程):与系统线程时映射关系

调度模型

M:N 把M个goroutine交给N个系统线程去执行

可增长栈

操作系统线程一般有固定的内存2M,一个goroutine在其生命周期开始是一个很小的栈(2k),goroutine的栈不是固定的,他可以按需增大和缩小。goroutine的栈最大限制可达到1G,虽然极少用到这么大。所以在go语言中一次创建十万个goroutine也是可以的

调度器策略:

1.复用线程
work stealing机制(偷取机制):当M0对应的本地队列中存在多个G等待执行,而M1里面无G,这个时候M1会先从全局队列里获取G(全局队列加锁效率会慢),如果全局队列也无G,则从M0对应的队列里偷取一个G来执行
image.png
head off机制(分离机制):当M0中存在阻塞任务,M1中也有任务正在执行,这个时候会创建/唤醒一个M2将M0对应的P转移过来。由阻塞的G执行完或者死掉后M0会被回收
image.png
2.利用并行

通过GOMAXPROCS来限定P的个数=CPU核数/2
runtime.GOMAXPROCS()进行设置,默认为CPU的逻辑核数,默认跑满整个CPU
3.抢占
image.png
传统协程是一个协程绑定一个CPU,当这个协程主动释放后下一个协程才可以绑定CPU
goroutine:一个G绑定cpu的最大时间为10ms,当超过10ms后等待的G可以来抢占CPU

创建

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. )
  6. func main() {
  7. //创建一个goroutine
  8. go func() {
  9. defer fmt.Println("输出")
  10. func() {
  11. defer fmt.Println("哈哈")
  12. //正常return只能退出当前函数不能退出goroutine,使用runtime.Goexit()可直接退出goroutine
  13. runtime.Goexit()
  14. fmt.Println("哈哈2")
  15. }()
  16. fmt.Println("输出2")
  17. }()
  18. i := 0
  19. for {
  20. i++
  21. }
  22. }