> For the complete documentation index, see [llms.txt](https://wskdsgcf.gitbook.io/mastering-go-zh-cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wskdsgcf.gitbook.io/mastering-go-zh-cn/10.0/10.1.md).

# 10.1 重温调度器

调度器负责以有效的方式在可用资源上分配要完成的工作量。这节，我们将检查 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 调度器如何工作](https://github.com/hantmac/Mastering_Go_ZH_CN/tree/master/images/chapter10/10.1-1.jpg)

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

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

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

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