谈谈协程切换
go 基于线程的基础上,在用户态设计用户态线程模型与调度器,减少了并发调度代价。但也带来goroutine非常规线性顺序执行的困惑,而这显然
runtime
调度 息息相关,本文旨在分析goroutine何时发生切换,以及与之相关的概念。
runtime
多个goroutine好比平行宇宙中的每个世界,而chan则是 一个穿梭机,方便你在平行世界穿梭,在go的空间内,runtime是上帝,主宰一切。所有对操作系统API的调度,都会被 runtime
层拦截以便达到高效调度。
用户态协程调度机制
- N:1 多个用户态协程运行在一个OS线程上
- 1:1 一个用户态协程对应一个OS线程
- M:N 任意数量的用户态协程可以运行在任意数量的OS线程上
goroutine 模型
M 系统线程
G Goroutine golang协程
P Context M与P的中介,实现M:N模型的关键
M必须拿到P才能够对G进行调度
调度点
golang采用的是第三种调度机制, 显然线程较多时,完全依赖系统调度开销大
Goroutine 在 system call 和 channel call 时都可能发生阻塞,当程序发生 system call,M 会发生阻塞,同时唤起(或创建)一个新的 M 继续执行其他的 G
当程序发起一个 channel call,程序可能会阻塞,但不会阻塞 M,G 的状态会设置为 waiting,M 继续执行其他的 G,当 G 的调用完成,会有一个可用的 M 继续执行它
goroutine上下文切换
上面的都是官话,一切操作都只是为了压榨CPU,谁让它那么快,又出现多核。
- 出现阻塞,意味CPU在当前执行域没活干了,它在干等,换go而言,调度器要goroutine上下文切换
chan 读写出现阻塞时,runtime 会隐式地进行上下文切换,而在极个别情况下,需要程序员显式编码操作,如下所示
至于切换到哪个goroutine,由调度器决定。但可以肯定的是对同一chan所相关的goroutine执行有序
// 显式交给调度器切换,否则有的goroutine就饿死了
runtime.Gosched()
Goroutine vs Python yield
编程语言都是基于操作系统,显然goroutine能做的,其它 语言也可以。
只不过,比之于java,python之类上古语言,go做的更多而已。
cmp | goroutine | python yield |
---|---|---|
成本 | Go 原生支持协程,通过 go func() 就可以创建一个 goroutine | Python 可以通过 gevent.spawn 来新建一个 coroutine,需要第三方库来支持 |
切换 | Goroutine 之间的通信更简单,通过 channel call 即可实现,上下文切换透明 | Python 需要 yield 来传递数据和切换上下文(通过一些库封装后对调用者来说也是透明的,比如:gevent/tornado) |
核数 | Goroutine 可以被多个线程调度,可以利用多核 | Python coroutine 只会使用一个线程,所以只能利用单核 |
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: