01
介绍
进程是在运行时的一个可执行程序的独立虚拟内存空间[1]。在 Linux 中,每个进程都被分配一个虚拟内存空间,其中包括栈空间、未使用内存、堆空间、BSS、DATA、TEXT 等。
线程可以被理解为轻量级进程,多个线程”寄生”在一个进程中,每个线程都有独立的栈空间,但其他虚拟内存空间是共享的。因此,线程之间的通信相对简单,可以通过共享内存进行通信。
进程和线程都是 CPU 的执行单元,在内核态下进行切换,因此切换的成本较高。
协程是用户态的一种伪执行单元,它在用户态下进行执行流程的切换,因此切换的成本较低。
02
切换执行单元的成本
我们通过介绍线程和协程的切换流程,讲述为什么在内核态切换的成本较高,而在用户态切换的成本较低?
因为进程和线程都是内核态切换,并且进程切换成本比线程切换成本更高,所以只介绍线程切换和协程切换的切换成本。
内核态切换 – 线程
在了解线程在内核态切换之前,我们先了解一下什么是 CPU 时间片[2],在操作系统中,我们会安装很多软件,并且我们会同时使用多个软件,而 CPU 资源有限。
为了让多个软件可以在操作系统中同时运行,CPU 分成一个个的时间片,在每个时间片中运行一个软件的一个线程,因为时间片非常短,所以我们会感觉多个软件在同时运行。
在编写代码时,我们为了可以让程序被分配到更多的 CPU 资源,可以多创建一些线程,用于提升程序运行的效率。需要注意的是,线程并不是创建越多越好。
因为 CPU 在内核态切换执行单元(线程)时,会有时间成本,在进行切换执行单元时,需要保存寄存器中的数据,将原执行单元的状态保存,切换操作也会占用 CPU 资源(时间片),从而减少了供线程运行的 CPU 资源(时间片)。
除了时间成本之外,还会有性能开销,系统内核调度线程,需要用户空间和内核空间切换,因为只有拥有最高权限的内核空间才可以调度线程,限于篇幅,我们不再展开叙述。
用户态切换 – 协程
因为通过创建线程(执行单元),为程序争取更多的 CPU 资源,在线程切换时也会浪费 CPU 资源(时间成本),所以可以将执行单元不再在内核态运行,改为在用户态运行,也就是协程。
协程的切换成本较低,是因为切换比较简单,并且是在用户态进行切换,切换的时间成本较低(纳秒级),只需将当前协程的 CPU 寄存器的状态先保存起来,然后将需要 CPU 资源的协程的 CPU 寄存器的状态加载到 CPU 寄存器中。
关于 Go 协程的调度,我们在之前的文章中介绍过,此处不再赘述。
03
内存占用
除了 CPU 资源有限之外,内存资源也是有限的,所以我们还需要了解进程、线程、协程的内存占用。
读者朋友们应该知道 32 位操作系统只支持 4G 内存的内存条,这是因为进程在 32 位操作系统中最多只能占用 4G 内存,而在 64 位操作系统中可以占用更多内存。
线程占用内存一般是 10MB,不同的操作系统版本之间有些差异,区间在 4M – 64M。
协程占用内存最小,一个协程占用 2KB 左右的内存。
04
总结
本文我们主要介绍为什么 Go 协程比进程和线程占用的系统资源低,通过进程、线程、协程的 CPU 资源和内存占用的比较,发现无论是在切换时消耗的 CPU 资源(时间片),还是内存占用,Go 协程都有明显优势。
一句话总结就是 Go 协程的切换成本和内存占用比线程和进程都低。
需要注意的是,Go 协程占用系统资源低,并不代表可以无限创建 Go 协程。