这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记
1.goroutine
线程成本高主要表现在以下两个方面:
- 调度的高消耗CPU;操作系统线程的创建和切换都需要进入内核,都会占用很长时间,导致CPU有很大的一部分时间都被用来进行进程调度了
- 高内存占用;内核在创建操作系统线程时默认会为其分配一个较大的栈内存(进程虚拟内存会占用4GB[32位操作系统], 而线程也要大约4MB)
而相对的,用户态的goroutine则轻量得多:
- goroutine是用户态线程,创建和切换都在用户代码中完成而无需进入操作系统内核
- goroutine启动时默认栈大小只有2k,可自动调整容量
2.GPM模型
- P: Processor
- M:Machine内核级线程
- G:goroutine协程
3.调度策略
- 我们通过 go func()来创建一个goroutine
- 有两个存储G的队列,一个是局部调度器P的本地队列、一个是全局G队列。新创建的G会先保存在P的本地队列中,如果P的本地队列已经满了就会保存在全局的队列中
- G只能运行在M中,一个M必须持有一个P,M与P是1:1的关系。M会从P的本地队列弹出一个可执行状态的G来执行,如果P的本地队列为空,就会向其他的MP组合偷取一个可执行的G来执行
- 一个M调度G执行的过程是一个循环机制,如下伪代码所示
- 当M执行某一个G时候如果发生了syscall或则其余阻塞操作,M会阻塞,如果当前有一些G在执行,runtime会把这个线程M从P中摘除(detach),然后再创建一个新的操作系统的线程(如果有空闲的线程可用就复用空闲线程)来服务于这个P
- 当M系统调用结束时候,这个G会尝试获取一个空闲的P执行,并放入到这个P的本地队列。如果获取不到P,那么这个线程M变成休眠状态, 加入到空闲线程中,然后这个G会被放入全局队列中