文档章节

goroutine的调度

秋风醉了
 秋风醉了
发布于 2016/09/23 11:57
字数 2094
阅读 56
收藏 0

goroutine的调度

概述

Go 1.1 重要特性之一就是由 Dmitry Vyukov 贡献的新调度器。无需对程序进行任何调整,新的调度器就可以为 Go 程序带来令人兴奋的性能提升。因此我觉得有必要就此写点什么。

在本博文所述的大多数内容都已经在原始的设计文档中有所介绍。那是一篇相当全面的文档,同时也相当专业。

你想要了解的关于新的调度器的一切都能在那篇文档里找到,而这篇博文描绘了整体情况,所以优略得所。

为什么 Go 运行时需要一个调度器

在了解新调度器之前,先要了解为什么需要它。为什么在操作系统已经能够对线程进行调度的情况下还需要创建一个用户空间调度器。

POSIX 线程 API 绝对是对已有的 Unix 进程模型的逻辑扩展,这样线程就获得了跟进程类似的控制方式。线程拥有自己的信号掩码,可以与 CPU 关联起来,可以放入 cgroups 或查询哪些资源被其使用。所有这些控制方式所带来的特性对于使用 goroutine 的 Go 程序来说都不需要,并且当程序有 100000 个线程的时候,所需的控制会急速膨胀。

另一个问题是 OS 不能基于 Go 模型根据实际情况进行调度。例如,Go 垃圾收集器在执行回收时,需要所有的线程都先停止,而内存也必须在一致的状态。这包含了等待正在运行的线程执行到某个已知内存会达到一致状态的地方。

当有许多线程进行随机的调度,挑战是你必须不停的等待他们达到一致状态。 Go 调度器可以决定在已知内存会一致的地方进行调度。这意味着当停下进行垃圾收集时,只需要等待在 CPU 内核上实际运行的线程。

我们的阵容

通常有三个线程模型。一个是 N:1,也就是若干个用户空间线程运行在一个 OS 线程上。它的好处是上下文切换非常迅速,而坏处是无法发挥多核系统。另一个是 1:1,也就是一个执行线程对应一个 OS 线程。好处是可以利用机器上的所有内核,不过由于它是通过 OS 来进行的,所以上下文切换非常慢。

Go 试图利用 M:N 调度器在两个世界中找到平衡点。若干 goroutine 调度在若干 OS 线程上。得到了快速的上下文切换,并且可以利用系统里的所有核心。而主要的问题是这个方法会增加调度器的复杂度。

为了完成任务的调度,Go 调度器使用了 3 个主要的实体:

输入图片说明

三角形代表 OS 线程。它是由系统管理执行的线程,并且工作方式与标准的 POSIX 线程相当类似。在运行时的代码里,叫做 M 代表设备。

圆形代表 goroutine。它包括了栈、指令指针和其他调度 goroutine 所需的重要信息,如可能阻塞它的任何一个 channel。在运行时代码里,它被叫做 G。

矩形代表调度的上下文。可以将其看作是一个在一个线程上运行 Go 代码的局部版本的调度器。这是从 N:1 调度器演化到 M:N 调度器的重要的一环。在运行时代码中,它被叫做 P 代表处理器。关于这部分还得再多说几句。

输入图片说明

这里有 2 个线程(M),每个都拥有一个上下文(P),每个都执行一个 goroutine(G)。线程必须拥有一个上下文才能执行 goroutine。

**上下文的数量是由环境变量 GOMAXPROCS 在启动的时候设置的,也可以通过运行时函数 GOMAXPROCS() 设置。事实上上下文的数量是固定的,这也就是说任何时候都只有 GOMAXPROCS 个 Go 代码在执行。**可以使用这个来在不同的计算机上进行调整,比如 4 核 PC 会运行 4 条 Go 代码的线程。

灰色的 goroutine 没有在运行,但是已经准备好被调度了。它们排列在一个叫做 runqueues 的列表里。当 goroutine 执行 go 语句时就会被添加到 runquque 的尾部。一个正在运行的 goroutine 到达调度点时,上下文就会从 runqueue 中弹出这个 goroutine,并且设置栈和指令指针,然后开始执行下一个 goroutine。

为了减少互斥争用,每个上下文都有它自己本地的 runqueue。上一个版本的 Go 调度器只有一个使用互斥量保护的全局 runqueue。线程经常为了等待互斥量解锁而被阻塞。当在一个 32 核的机器上想要尽可能的压榨性能时这会变得非常糟糕。

只要上下文有 goroutine 需要运行,调度器就会在这个稳定的状态下持续的进行调度。然而,有一些情况可能会改变这个局面。

你要(系统)调用谁?

你现在可能在想,为什么需要上下文?为什么不能抛开上下文直接将 runqueue 放在线程上?其实不是这样的。有上下文的原因是当由于某些原因正在执行的线程会阻塞时可以切换到其他线程。

一个关于阻塞的例子就是系统调用。由于线程无法在运行代码的同时又阻塞在系统调用上,所以需要上下文进行切换来保证调度。

输入图片说明

这里可以看到一个线程放弃了它的上下文,因此其他线程可以运行。调度器保证了有足够的线程运行所有的上下文。为了正确的处理系统调用,会创建或者是从线程缓存中获取上图中的 M1。技术上说被系统调用线程持有的进行了系统调用的 goroutine 仍然是在运行的,尽管在 OS 层它被阻塞了。

当系统调用返回,线程必须尝试获取上下文以便让 goroutine 继续运行。通常的模式是从其他线程窃取一个上下文。如果没办法偷得到,就会将 goroutine 放入全局 runqueue 中,然后自己返回线程缓存继续休眠。

当上下文执行完本地的 runqueue 后,会从全局 runqueue 获取 goroutine。上下文也会定期的检查全局 runqueue。否则的话在全局 runqueue 上的 goroutine 可能由于缺乏资源而永远都不会运行。

这个处理系统调用的方法说明了为什么即使 GOMAXPROCS 为 1 的时候,Go 程序也会运行多个线程。运行时用 goroutine 调用系统调用,而让线程藏在背后。

窃取工作

当一个上下文调度执行完所有的 goroutine 时系统的稳定状态也会发生变化。这发生在上下文的 runqueue 分配的工作不平衡的时候。这会导致当上下文清空其 runqueue 后,在系统中仍然有工作需要完成。为了让 Go 代码继续执行,上下文可以从全局 runqueue 获取 goroutine,但是如果没有 goroutine 在其中的话,总得从其他什么地方获取到它们。

输入图片说明

这个其他地方其实就是其他上下文。当一个上下文执行完,它会试图偷取其他上下文的一半 runqueue。这保证了每个上下文都总是有工作可做,也保证了所有的线程都进其最大的能力在工作。

何去何从?

还有许多调度器的细节,如 cgo 线程,LockOSThread() 函数和带有网络池的指令。它们不在本文讨论的范围内,但是仍然值得学习。以后我可能会写一些关于这些的内容。在 Go 运行时库中还有许许多多有趣的创造等待着被探索。

本文转载自:http://mikespook.com/2013/07/%E7%BF%BB%E8%AF%91go-%E7%9A%84%E8%B0%83%E5%BA%A6%E5%99%A8/

共有 人打赏支持
秋风醉了
粉丝 246
博文 543
码字总数 412294
作品 0
朝阳
程序员
私信 提问
Goroutine调度实例简要分析

Goroutine调度实例简要分析 Tony Bai2017-11-235 阅读 githubAssemblyGolangGCGoCompiler 前两天一位网友在 微博 私信我这样一个问题: 抱歉打扰您咨询您一个关于 Go 的问题:对于goroutine...

Tony Bai
2017/11/23
0
0
Go work-stealing 调度器

本文译自 Rakyll 的 scheduler版权@归原文所有. Go 调度器的工作是将可运行的 goroutine 分发到一个或多个处理器上运行的多个操作系统工作线程. 在多线程计算里, 调度出现了两种模式: work-...

A Coder @ Work
2018/01/31
0
0
Go基础编程:并发编程—goroutine

1 goroutine是什么 goroutine是Go并行设计的核心。goroutine说到底其实就是协程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的...

tennysonsky
2018/01/15
0
0
Goroutine调度器(二):调度流程简述

文章目录 启动过程 创建goroutine(G) 创建内核线程(M) 调度核心 调度点 现场处理 我们都知道Go语言是原生支持语言级并发的,这个并发的最小逻辑单元就是goroutine。goroutine就是Go语言提供的...

John
2018/05/23
0
0
Go语言并发与并行学习笔记(二)

目录(?) [-] Go语言的并发和并行 goroutine是在并行吗 并行和并发 真正的并行 一个小问题 runtime调度器 总结 开启多核的实验 Go语言的并发和并行 不知道你有没有注意到一个现象,还是这段代...

nop4ss
2015/07/23
64
0

没有更多内容

加载失败,请刷新页面

加载更多

Coding and Paper Letter(六十四)

资源整理。 1 Coding: 1.交互式瓦片编辑器。 tile playground 2.R语言包autokeras,autokeras的R接口。autokeras是一个开源的自动机器学习的软件。 autokeras 3.斯坦福网络分析平台,用于网络...

胖胖雕
37分钟前
0
0
最简单的cd命令是个大坑!

BASH Shell 是大多 Linux 发行版的默认 shell,BASH 有一些自己的内置命令,cd 就是其中的一个。 在centos6里面,系统中不存在 cd 的二进制文件。但是你仍然可以运行该命令,这是因为 cd 是 ...

gaolongquan
48分钟前
1
0
spring获取bean的几种方式

使用jdk:1.8、maven:3.3.3 spring获取Bean的方式 pom.xml文件内容: <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="......

Vincent-Duan
55分钟前
2
0
一段话系列-Linux中IO的同步、异步、阻塞、非阻塞

首先我们框定一下背景,我们探讨的是Linux系统下的IO模型。 同步和异步是针对内核操作数据而言的,同步是指内核串行顺序操作数据,异步是指内核并行(或并发)操作数据,然后通过回调的方式通...

EasyProgramming
59分钟前
5
0
好程序员web前端分享主流CSS image比较

好程序员web前端分享主流CSS image比较在还原设计图的时候,难免会碰到一些样式图片的引用。如何来对这些图片做优化呢?本文简单的梳理了一下目前几种比较常用的使用方式。   注: 1. 有更好...

好程序员IT
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部