Go 语言进阶:并发编程 之 细🔒说协程
这一节部分内容参考、翻译自 Goroutines Under The Hood 。
前置知识:并发与并行;线程;线程/进程调度基础知识。
我们在课堂上学到了 goroutine 是协程,老师告诉我们它和线程的区别是,协程在用户态,线程在内核态……
SO? YOU WILL BE LIKE THEM? 所以呢?这有什么区别?为什么用协程要比只用线程快?按理来说线程更底层,从直觉上来讲更底层意味着速度更快才对,凭什么协程会比线程更好?
协程的魔法
在计算机网络中我们学到很多有关多路复用的技术,不论是从直觉上还是事实上,复用确实是一个提高效率的方法,协程就把复用的思想玩进了 concurrent programming(并发编程)中。
当一个协程需要被阻塞时,运行时会把这个线程上的其他等待运行的协程,移动到其他可以运行程序的线程上,所以其他协程不会被卡住。
这句话消化起来可能有些困难,但你只要看这个案例 🌰:
注意:
所有线程都是内核级的。
下面的流程是一个简化 / 假设的版本。
-
我们手头有四个 goroutine(Go 协程),我们希望用内核级的线程来执行他们。同样假设我们只用 2 个线程,并且只有一个处理机。
-
现在我们想让这些 goroutine 运行在线程上,比如 A 和 B 用左边,D 和 C 用右边。每一组用框框起来的 goroutine 从下往上运行,比如左边那组先 A 后 B 。
-
现在时间开始流动,A 和 C goroutine 开始运行,但是 A goroutine 发动了陷阱卡,调用了一个需要阻塞的系统调用,现在由于 A 阻塞了,所以 B 也没法运行,相当于也阻塞了。
-
与此同时,C 运行结束
-
Go 的运行时现在发现,B 在左边一直等 A,可能得等好久才行,还不如放在右边一下跑完了
-
D 运行结束
-
B 现在也运行结束了,左边的线程还在嗯等 A 的阻塞结束
到现在这个时刻,时刻如果运行时没有执行“把这个线程上的其他等待运行的协程,移动到其他可以运行程序的线程上”这个操作,B 就还在等 A 阻塞。
这是个极致简单的例子,但对开发者来说可能成为盲点,对于运行上千 goroutine 的强大系统来说,这个机制会带来巨大的收益。
原文博客中,还提到了 goroutine 在空间上的开销很小的优势,但是本文侧重调度就不翻译了,感兴趣的同学推荐阅读原文。实在是翻不动了
昂贵的线程
这部分内容实际上来自原博客所对应的 Hacker News 讨论。
到目前为止,我们还有一个问题不太清楚,那就是“为什么用协程要比只用线程快?按理来说线程更底层,从直觉上来讲更底层意味着速度更快才对,凭什么协程会比线程更好?”
在不掌握操作系统专家级知识的前提下,我们从直觉上来看,CPU(处理器)通常会把线程切换这样的工作看作一条指令,运行一条指令的速度应该是极快的。
解答
实际上,任何内核态的上下文切换开销都是很大的,而且远远不止会运行一条指令,切换上下文时内核也有一包子事要做,不仅仅是换个正在运行的线程这样。内核级的上下文切换开销通常是用户态(就是不进内核级)下上下文切换的 10 倍。
其他推荐内容
-
Java 19 中对应 Go 并发编程的示例:mccue.dev/pages/5-2-2…
-
Go 在糟糕的网络环境下是 😈 邪恶的:withinboredom.info/blog/2022/1…
-
goroutine 分析器。由 Java TDA 和 goroutine-inspec 启发:github.com/openziti/go…
-
在 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
}
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.