GPM模型:
- G,Goroutinue
- 被调度器管理的轻量级线程,goroutine使用go关键字创建
- 调度系统的最基本单位goroutine,存储了goroutine的执行stack信息、goroutine状态以及goroutine的任务函数等。默认的大小是2KB,根据需要逐步上涨。
- G绑定到P上执行
- P,Processor
- 逻辑执行单元
- 存储了M执行的上下文,包括各种G对象队列、链表、cache和状态
- G存在于P中的特定链表上,同一时刻,P只能在一个M上,因此不需要锁
- M,Machine
- 操作系统的实际的线程
- OS的执行的单位,Linux下的大小是8MB
- M绑定执行的P,但是不保存P的状态,因此P可以跨M执行
每个M都有一个P,绿色的G表示当前M上运行的G,灰色的表示local G queue
runtime.GOMAXPROCS
go
如果G发生阻塞,则会尝试寻找新的G来运行,阻塞的G返回后重新加入G Queue中。
P在轮询查找G的时候,每隔61次从Global G Queue中查找,保证Global也可以执行。当一个G执行超过10ms时,schedule会有对应的抢占机制。
一些底层知识线程切换与协程切换的区别。LTS(Local Thread Storage)存储了线程执行需要的堆栈信息,寄存器的数据等,之后线程会load 程序并执行。对于执行中的进程,在对应的地址其实位置,同样会启动线程,此时OS会分配对应的内存空间,并启动执行。Linux系统中,1个线程是启动的大小8MB,而且启动和上下文切换会消耗对应的时间。
协程的特点,协程不是OS级别的,因此协程的功能是程序内部调度的。OS感觉不到协程的存在,因为OS根本就没有协程的概念!!!
Golang为协程的代码段,在堆上分配初始化的2KB的空间,之后进入之前提到的调度流程。一般来说,Golang使用了线程复用的方式,即启动线程的时候,在上面有协程运行,协程停止或者阻塞的时候,不会主动停止线程,而是更改线程的FS寄存器的值到对应的协程代码段上,然后此时线程执行的位置就是新的协程的代码位置了,此时协程切换的代价是改变线程执行的位置,然后执行新的协程,因此代价很小。
这边可能要后期更正,FS寄存器那边的概念不是特别清楚
具体调度方案sysmon
以下几种情况会导致Goroutinue阻塞,进而让出P,使得P与其它G绑定,高效利用CPU:
- syscall:系统调用,比如读写长文本等
- Network IO:网络传输数据等
- channel获取不到数据
- sync包的调用
- https://zboya.github.io/post/go_scheduler/
- https://blog.csdn.net/u010853261/article/details/84790392#Section1_Scheduler_9
- http://www.sizeofvoid.net/goroutine-under-the-hood/