传统的编程语言,比如 C、C++ 等的并发实现,多是基于线程模型的,也就是应用程序负责创建线程(一般通过 libpthread 等库函数调用实现),操作系统负责调度线程。当然,我们也说过,这种传统支持并发的方式有很多不足。为了解决这些问题,Go 语言中的并发实现,使用了 Goroutine,代替了操作系统的线程,也不再依靠操作系统调度。Goroutine 占用的资源非常小,上节课我们也说过,每个 Goroutine 栈的大小默认是 2KB。而且,Goroutine 调度的切换也不用陷入(trap)操作系统内核层完成,代价很低。因此,一个 Go 程序中可以创建成千上万个并发的 Goroutine。而将这些 Goroutine 按照一定算法放到“CPU”上执行的程序,就被称为 Goroutine 调度器(Goroutine Scheduler),注意,这里说的“CPU”打了引号。
一个 Go 程序对于操作系统来说只是一个用户层程序,操作系统眼中只有线程,它甚至不知道有一种叫 Goroutine 的事物存在。所以,Goroutine 的调度全要靠 Go 自己完成。那么,实现 Go 程序内 Goroutine 之间“公平”竞争“CPU”资源的任务,就落到了 Go 运行时(runtime)头上了。要知道在一个 Go 程序中,除了用户层代码,剩下的就是 Go 运行时了。
Goroutine 的调度问题就演变为,Go 运行时如何将程序内的众多 Goroutine,按照一定算法调度到“CPU”资源上运行的问题了。可是,在操作系统层面,线程竞争的“CPU”资源是真实的物理 CPU,但在 Go 程序层面,各个 Goroutine 要竞争的“CPU”资源又是什么呢?Go 程序是用户层程序,它本身就是整体运行在一个或多个操作系统线程上的。所以这个答案就出来了:Goroutine 们要竞争的“CPU”资源就是操作系统线程。这样,Goroutine 调度器的任务也就明确了:将 Goroutine 按照一定算法放到不同的操作系统线程中去执行。
此文章为6月Day026学习笔记,内容来源于极客时间《Tony Bai · Go 语言第一课》,强烈推荐该课程!