Goroutine调度分析(三)

来源:互联网 发布:ug软件怎么下载 编辑:程序博客网 时间:2024/05/17 12:03

  上一篇文章中,大致讲了goroutine的C代码数据结构以及大致的调度策略,但是其实上一篇所提到的知识一开始go的设计,还有许多性能上的缺点。高吞吐量的其服务器以及并行运算,显示14%的的时间用在了runtime.futex()。

scheduler 的问题

  1. 一个全局的互斥锁和集中的状态。互斥锁保护goroutine的操作。
  2. Goroutine hand-off。工作线程经常之间传递runnable goroutine,这会导致延迟增加,和资源消耗。
  3. 每一个M都有mcache。其实只有正在运行goroutine的M才需要用到mcache(一个因为系统调用阻塞的M的并不需要mcache)。通常来说跑G code的M和所有的M的数量比有1:100,或者更大。这就导致过多的资源消耗(每一个Mcache占用炒作2MB)。和很差的data locality.
  4. 过多的线程blocking/unblocking。

设计改进

大致上来说就是增加一个P(Processors)数据结构并且实现一个work-stealing scheduler。
  M代表OS线程,P代表一个需要执行Go的资源。当M执行Go code,它必须有一个P。若M是空闲的或者进入系统调用,它也需要P。
  确切来讲,总共有GOMAXPROCS 个P。所有的P被组成一个数组,这是work-stealing的要求。一些变量从sched移动到P。一些变量也从M移动到P(一些跟激活go code执行相关的变量)。P数据结构如下图
这里写图片描述
当一个M准备要执行Go code时,它必须从list中pop一个P。当执行完Go code时,它必须将它的P push到list中。所有当M执行Go code,它必须有一个P。

调度

  当新的G被创建或者一个G编程runnable状态。它会被push到当前执行的P。当P执行完一个G,它会先尝试从自己list中pop一个runnable G。如果list是空的,则P选择一个随机的P(不包括自己),且尝试偷一半的runnable goroutine过来。

系统调用/ M Parking 和 Unparking

  当M创建一个新的G,必须要保证有一个其他M来执行这个G。类似的,当M进入系统调用,它必须保证有一个其他的M来执行Go code。
这里有两个选择,一个是block 或unblock M, 一个是实现spinning(自旋)。这里有一个性能和消耗不必要CPU周期的冲突。这里的idea是用spinning并且需要消耗cpu周期。尽管如此,当GOMAXPROCES=1时,二者是没有区别的。
Spinning有两个阶段:(1)当有P资源的空闲M寻找新的G (2)当M没有P资源等待可用的P资源时。 这里至多有GOMAXPROCS spinning的M。当有空闲的type(2)造成的空前M时,type(1)产生的M不会阻塞。当有一个G被创建时,或者M进入了系统调用,或M从空闲转为忙碌,它会保证至少有一个spinningM。这就保证了没有runnable goroutine会被执行,并且避免了过多的blocking/unblocking

LockOSThread

  1. Locked G转为non-runnable。M立即返回P到空闲列表,唤醒其他M并且阻塞
  2. Locked G 转为runnable。当前M将自己的P和locked G交给跟locked G链接的M,并且Unlock 它。当前的M转为空闲

空闲G

  runtime有一个空闲G队列,M会访问这个队列当它有几次失败地偷G的尝试之后。

原创粉丝点击