【译】GO的工作窃取调度程序 - Go语言中文社区

【译】GO的工作窃取调度程序


[toc]

前言

原文:Go's work-stealing scheduler

Go scheduler的工作是向运行在一个或多个处理器上的OS线程上分发可运行的goroutines。在多线程计算中,调度会出现两种形式:工作分享和工作窃取。

  • 工作分享:当处理器生成新线程时,它会尝试将其中一些线程迁移到其他处理器,并希望它们被闲置或未充分利用的处理器处理。
  • 工作窃取:未充分利用的处理器主动寻找其他处理器的线程并“窃取”一些。

与工作共享相比,工作窃取的线程迁移发生频率较低。当所有处理器都有工作要运行时,不会迁移任何线程。只要有空闲处理器,就会考虑迁移。

Go从1.1版本开始加入工作窃取调度,由Dmitry Vyukov提供。本文件将深入解释什么是“work-stealing” 以及go是如何实现的

调度基础知识

Go有一个M:N调度程序,可以使用多个处理器。在任何时候,M goroutines需要在N OS线程上进行调度,这些线程最多可运行处理器的GOMAXPROCS个。Go scheduler对goroutines,线程和处理器使用以下术语:

  • G: goroutine
  • M: OS thread (machine)
  • P: processor

这里有一个特定的本地P和一个全局goroutine队列。每个M需要分配给P。这些P如果被阻塞或正在执行系统调用则可能没有M,在任何时候最多存在GOMAXPROCS个P。在任何时候每个P上只能有一个M,如果需要,调度程序可以创建更多的M。

image

每轮调度只是简单的找到一个可运行的goroutine并执行它。在每轮调度中,搜索按以下顺序进行:

runtime.schedule() {
    // only 1/61 of the time, check the global runnable queue for a G.
    // if not found, check the local queue.
    // if not found,
    //     try to steal from other Ps.
    //     if not, check the global runnable queue.
    //     if not found, poll network.
}

一旦找到可运行的G,就会执行它直到它被阻止。

注意:看起来全局队列比本地队列有优势,但是为了避免M只在本地队列,直到没有本地队列goroutine剩余,检查一遍全局队列也是至关重要的。

窃取

当创建一个新的G或现有G变为可运行时,它被推送到当前P的可运行goroutine列表。当P完成执行G时,它尝试从自己的可运行goroutine列表中弹出G. 如果列表现在为空,则P选择随机的其他处理器(P)并尝试从其队列中窃取一半可运行的goroutine。

image

在上面的例子中,P2找不到任何可运行的goroutines。因此,它随机选择另一个处理器(P1)并窃取三个goroutine到它自己的本地队列。P2将能够运行这些goroutine,并且调度程序工作将在多个处理器之间更公平地分布。

旋转线程(spinning threads)

调度程序总是希望向M分配尽可能多的可运行的goroutine以利用处理器,但同时我们需要停止过多的工作来节省CPU和电源。与此相反,调度程序还需要能够处理高吞吐量和CPU密集型程序。

如果性能至关重要,则常量抢占是昂贵的且对于高吞吐量程序也是一个问题。操作系统线程不应经常在彼此之间切换可运行的goroutine,因为它会导致延迟增加。除了存在系统调用之外,OS线程需要不断被阻塞和解除阻塞。这是代价高昂的并且增加了很多开销。

为了最大限度地减少切换,Go调度程序实现了“旋转线程”。旋转线程消耗一些额外的CPU功率,但它们最小化了OS线程的抢占。在以下情况下线程正在旋转

  • 被分配了P的M,正在寻找可运行的goroutine。
  • 没有P分配的M正在寻找可用的P。
  • 如果有空闲的P并且没有其他旋转线程,调度程序也会取消停放一个额外的线程并在准备goroutine时旋转它。

在任何时刻最多有GOMAXPROCS个旋转M。当旋转线程找到工作时,它会使自己脱离旋转状态。

如果存在P赋值的空闲M,则具有P赋值的空闲线程不会阻塞。当创建新的goroutine或M阻塞时,调度程序确保至少有一个旋转M.这确保没有可运行的可运行的goroutine;并避免过多的M阻塞/解除阻塞。

结论
Go调度程序做了很多工作,以避免过多抢占操作系统线程,方法是通过窃取将它们安排到正确和未充分利用的处理器,以及实现“旋转”线程以避免阻塞/解除阻塞过渡的高频发生。

execution tracer可以跟踪调度事件。如果您碰巧认为处理器利用率较低,您可以调查发生了什么。

参考:

版权声明:本文来源简书,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://www.jianshu.com/p/ca126bb8e078
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-01-12 12:52:58
  • 阅读 ( 829 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢