我们从一篇文章出发:Golang CSP并发模型
Go 是一个面向并发的语言,借用了 CSP 并发模型的原理,通过 goroutine 进行并发,channal 进行通信。
由计算机操作系统的基本知识和文章中的介绍,我们可以知道,线程分为用户线程和内核线程,用户线程和内核线程的关系可以是一对一、多对一和多对多,而 goroutine 和线程的关系并不一对一,而是使用协程的方式,通过 Go 调度器来实现 goroutine 与线程的多对多关系。
Go 的调度器有过一次重大改版,对于原始调度器的问题和新调度器的计划可以看 Go 新调度器的作者 Dmitry Vyukov 的 Scalable Go Scheduler Design Doc(虽然是英文比较难啃)。
已经有大佬对 Go 调度器相关内容进行过分析,参照 Golang/Go goroutine调度器原理/实现,作者对新旧两种调度器模型进行了讲解,重点还是在于新的调度器,也就是一直沿用的 G-P-M 调度器模型,其最大的改变在于对 1.0 中的 G-M 模型增加了一个 P (processer) 抽象层作为逻辑处理器,解决了 G (goroutine) 与 M (machine) 绑定的问题。
大佬的文章中说到了 Go 调度器在 Go 1.2 中实现的 goroutine 抢占调度是通过在函数入口加入一段代码来实现的,因此对于没有函数调用的死循环是不会被抢占的,目前在 Go 1.14 中,实现了异步 gouroutine 抢占,通过随机的系统信号进行检测。但是抢占只能发生在 runtime 能够 cover 到的地方,当通过 syscall 或者其他包进行系统调用时被抢占则调用会返回错误
EINTR
,对于可重入的系统来说,接收到错误之后需要对操作进行重试 https://www.codercto.com/a/104059.html
个人对 G-P-M 模型的理解可以见另一篇文章:
steal working