G=goroutine:里面出坑了存储goroutine以外还存储了P的关联信息
P=PROCESS:管理一组G,P里面会存储goroutine运行的上下文环境(函数指针、堆栈地址及地址边界),P会对G做一些调度策略。P维护一个runq_用来存放等待执行的goroutine,新创建的G会优先放在P的本地队列,当本地队列满(256G)时,会放入G的全局队列
M=Thread=Machine(系统线程):与系统线程时映射关系
调度模型
可增长栈
操作系统线程一般有固定的内存2M,一个goroutine在其生命周期开始是一个很小的栈(2k),goroutine的栈不是固定的,他可以按需增大和缩小。goroutine的栈最大限制可达到1G,虽然极少用到这么大。所以在go语言中一次创建十万个goroutine也是可以的
调度器策略:
1.复用线程
work stealing机制(偷取机制):当M0对应的本地队列中存在多个G等待执行,而M1里面无G,这个时候M1会先从全局队列里获取G(全局队列加锁效率会慢),如果全局队列也无G,则从M0对应的队列里偷取一个G来执行
head off机制(分离机制):当M0中存在阻塞任务,M1中也有任务正在执行,这个时候会创建/唤醒一个M2将M0对应的P转移过来。由阻塞的G执行完或者死掉后M0会被回收
2.利用并行
通过GOMAXPROCS来限定P的个数=CPU核数/2
runtime.GOMAXPROCS()进行设置,默认为CPU的逻辑核数,默认跑满整个CPU
3.抢占
传统协程是一个协程绑定一个CPU,当这个协程主动释放后下一个协程才可以绑定CPU
goroutine:一个G绑定cpu的最大时间为10ms,当超过10ms后等待的G可以来抢占CPU
创建
package main
import (
"fmt"
"runtime"
)
func main() {
//创建一个goroutine
go func() {
defer fmt.Println("输出")
func() {
defer fmt.Println("哈哈")
//正常return只能退出当前函数不能退出goroutine,使用runtime.Goexit()可直接退出goroutine
runtime.Goexit()
fmt.Println("哈哈2")
}()
fmt.Println("输出2")
}()
i := 0
for {
i++
}
}