day2 细🔒协程的抢占式调度 | 青训营笔记

268 阅读4分钟

Go 语言进阶:并发编程 之 细🔒说协程

这一节部分内容参考、翻译自 Goroutines Under The Hood

前置知识:并发与并行;线程;线程/进程调度基础知识。

我们在课堂上学到了 goroutine 是协程,老师告诉我们它和线程的区别是,协程在用户态,线程在内核态……

SO? YOU WILL BE LIKE THEM? 所以呢?这有什么区别?为什么用协程要比只用线程快?按理来说线程更底层,从直觉上来讲更底层意味着速度更快才对,凭什么协程会比线程更好?

协程的魔法

在计算机网络中我们学到很多有关多路复用的技术,不论是从直觉上还是事实上,复用确实是一个提高效率的方法,协程就把复用的思想玩进了 concurrent programming(并发编程)中。

当一个协程需要被阻塞时,运行时会把这个线程上的其他等待运行的协程,移动到其他可以运行程序的线程上,所以其他协程不会被卡住。

这句话消化起来可能有些困难,但你只要看这个案例 🌰:

注意:

  • 所有线程都是内核级的。

  • 下面的流程是一个简化 / 假设的版本。

  1. 我们手头有四个 goroutine(Go 协程),我们希望用内核级的线程来执行他们。同样假设我们只用 2 个线程,并且只有一个处理机。

  2. 现在我们想让这些 goroutine 运行在线程上,比如 A 和 B 用左边,D 和 C 用右边。每一组用框框起来的 goroutine 从下往上运行,比如左边那组先 A 后 B 。

  3. 现在时间开始流动,A 和 C goroutine 开始运行,但是 A goroutine 发动了陷阱卡,调用了一个需要阻塞的系统调用,现在由于 A 阻塞了,所以 B 也没法运行,相当于也阻塞了。

  4. 与此同时,C 运行结束

  5. Go 的运行时现在发现,B 在左边一直等 A,可能得等好久才行,还不如放在右边一下跑完了

  6. D 运行结束

  7. B 现在也运行结束了,左边的线程还在嗯等 A 的阻塞结束

到现在这个时刻,时刻如果运行时没有执行“把这个线程上的其他等待运行的协程,移动到其他可以运行程序的线程上”这个操作,B 就还在等 A 阻塞。

这是个极致简单的例子,但对开发者来说可能成为盲点,对于运行上千 goroutine 的强大系统来说,这个机制会带来巨大的收益。

原文博客中,还提到了 goroutine 在空间上的开销很小的优势,但是本文侧重调度就不翻译了,感兴趣的同学推荐阅读原文。实在是翻不动了

昂贵的线程

这部分内容实际上来自原博客所对应的 Hacker News 讨论。

到目前为止,我们还有一个问题不太清楚,那就是“为什么用协程要比只用线程快?按理来说线程更底层,从直觉上来讲更底层意味着速度更快才对,凭什么协程会比线程更好?”

在不掌握操作系统专家级知识的前提下,我们从直觉上来看,CPU(处理器)通常会把线程切换这样的工作看作一条指令,运行一条指令的速度应该是极快的。

解答

实际上,任何内核态的上下文切换开销都是很大的,而且远远不止会运行一条指令,切换上下文时内核也有一包子事要做,不仅仅是换个正在运行的线程这样。内核级的上下文切换开销通常是用户态(就是不进内核级)下上下文切换的 10 倍

其他推荐内容

  1. Java 19 中对应 Go 并发编程的示例:mccue.dev/pages/5-2-2…

  2. Go 在糟糕的网络环境下是 😈 邪恶的:withinboredom.info/blog/2022/1…

  3. goroutine 分析器。由 Java TDA 和 goroutine-inspec 启发:github.com/openziti/go…

  4. 在 Go 里你永远无法安全地做字符串三路比较:go101.org/blog/2022-1…

一个三路比较的示例:

func Compare(a, b string) int {
	// NOTE(rsc): This function does NOT call the runtime cmpstring function,
	// because we do not want to provide any performance justification for
	// using strings.Compare. Basically no one should use strings.Compare.
	// As the comment above says, it is here only for symmetry with package bytes.
	// If performance is important, the compiler should be changed to recognize
	// the pattern so that all code doing three-way comparisons, not just code
	// using strings.Compare, can benefit.
	if a == b {
		return 0
	}
	if a < b {
		return -1
	}
	return +1
}

CC0
To the extent possible under law, Nico has waived all copyright and related or neighboring rights to this article. This work is published from: China Mainland.