谈谈协程切换

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 协议》,转载必须注明作者和本文链接
pardon110
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
开发者 @ 社科大
文章
133
粉丝
24
喜欢
100
收藏
54
排名:107
访问:8.9 万
私信
所有博文
社区赞助商