goroutine
go协程的本质是用户态的线程,相比于传统的内核态线程,在性能方面有更多优点
- 协程的切换发生在用户态,不用切换到内核态,不用处理时钟中断,效率更高。
- 协程栈空间更小(go支持协程栈的自动增长),一般在4KB左右。而线程栈一般在4MB左右。从而可以创建大量协程。
但是从程序员的角度来看,协程和线程用起来体验差不多。
GMP调度模型
- G(goroutine):协程。
- M(machine):实际跑任务的机器,对应一个线程。
- P(processor):调度器,里面保存了运行队列,抽象地看相当于是一个处理器。
一个P绑定到一个M上,每个P都会维护一个本地的G队列,这样每次从队列中取出G时不用竞争。如果本地队列空了,那么就会去全局G队列取一些过来。
每当进行磁盘IO,网络IO,协程休眠时协程就会让出,被调度。
抢占式调度
- 栈增长时调度:在go1.14之前就实现了,如果一个协程长时间占据CPU,那么在调用函数的时候执行编译器插入的
runtime.morestack
栈增长函数,里面会判断是否进行抢占式调度。 - 基于信号的抢占式调度:go1.14新特性,对于恶意的抢占程序,比如死循环,上面的方式也无法抢占。于是基于操作系统信号机制,当协程长时间占据CPU,那么给对应的线程发送
sigurg
信号,在注册的信号处理函数里面实现协程的切换。