Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
31 lines (16 sloc) 4.55 KB

重温调度器

调度器负责以有效的方式在可用资源上分配要完成的工作量。这节,我们将检查 Go 调度器的操作方式比上一章更深入些。您已经知道了 Go 工作使用 m:n 调度器(或M:N 调度器)来调度 goroutines——它比系统线程(使用系统线程)更轻量。然而先上我们来复习一些必要理论和一些有用的术语定义。

Go 使用 fork-join 并发模型。这个模型的 fork 部分代表一个程序在任何时间点创建的子分支。类似地,Go 并发模型的 join 部分是子分支将要接受的位置并加入它的父级。除此之外,收集 goroutines 结果的 sync.Wait() 和 channles 都是连接点,而任何新 goroutine 都会创建子分支。

fork-join 模型的 fork 阶段和 C 语言中的fork(2) 调用完全是两回事

公平调度策略是相当直接的,并有一个简单的实现,它在可用的处理器间均匀地共享所有负载。首先,这可能看起来是完美的策略,因为当它保持所有的处理器被均等占用时不必考虑太多事情。事实证明情况并非如此,因为大多数分布式任务通常依赖于其他任务。因此,最终一些处理器使用不足或者说一些处理器使用过剩。

在 Go 中 goroutine 是一个任务,而调用 goroutine 之后的所有内容是一个延续。在 Go 调度器使用的 work stealing 策略 中,一个未充分利用的(逻辑)处理器会从其他处理搜寻额外的工作。当它找到了这些工作,它就会从其他处理器偷取它们,这就是 work stealing 策略名称当由来。还有 Go 队列和窃取延续的 work-stealing 算法。一个间歇接合点,如其名所示,是一个执行线程在接合处间歇的点并开始寻找其他工作去做。尽管所有的任务窃取和延续窃取都有间歇接合点,但延续部分发生的更频繁;因此,Go 算法与延续部分工作而不是任务。

延续窃取的主要缺点是它需要来自编程语言的编译器的额外工作。幸运地,Go 提供了额外的帮助,因此在它的 work-stealing 策略中使用 延续窃取

延续窃取的一个好处是当您尽使用函数或具有多个 goroutine 的单个线程时,您可用获得相同的结果。这是完全合理的,因为这这两种情况下,在任何给定的时间都只执行一件事。

现在,让我们回到 Go 中使用的 m:n 调度算法。严格讲,在任何时候,您都有 m 个 goroutines 被执行并计划运行在 n 个系统线程上,使用最多 GOMAXPROCS 个逻辑处理器。稍后您将了解 GOMAXPROCS

Go 调度器使用三种主要实体工作:系统线程(M)与使用的操作系统有关,goroutines(G)和 逻辑处理器(P)。Go 程序能使用的处理器数量由 GOMAXPROCS 环境变量定义——在任何给定时间里有最多 GOMAXPROCS 个处理器。

下图说明了这点:

Go 调度器如何工作

这个插图告诉我们有俩个不同类型的队列:一个全局队列和一个关联到每个逻辑处理器的本地队列。全局队列里的 Goroutines 为了执行会被分配到逻辑处理器的队列中。因此,Go 调度器为了避免正在执行的 goroutines 只在每个逻辑处理器的本地队列中,需要检查全局队列。然而,全局队列不是总在被检查,意思是它没有比本地队列更有优势。另外,每个逻辑处理器有多个线程,并且窃取发生在可用逻辑处理器的本地队列之间。最后,注意当需要时, Go 调度器被允许创建跟多的系统线程。然而,系统线程相当昂贵,就是说处理系统线程太多会使您的 Go 应用变慢。

注意在程序中为了性能使用更多的 goroutines 不是万能方法,因为除了各种调用 sync.Add()sync.Wait(),和 sync.Done() 外,更多的 goroutines 由于需要 Go 调度器做额外工作会使您的程序变慢。

Go 调度器,作为大多数 Go 组件总是在进化,就是说负责 Go 调度器的人不断努力改进它的性能,通过对它的工作方式做微小改变。然而这个核心原理保持一致

所有这些细节有用吗?我想是的!为了使用 goroutines 写代码您需要知道所有这些吗?当然不!然而,当奇怪的事情发生时,或者您对 Go 调度器是如果工作的好奇,知道场景背后发生了什么一定能帮到您。这当然能使您成为更好的开发者!

You can’t perform that action at this time.