从栈切换的角度理解进程和协程
函数在执行时,会在栈上创建栈帧,并将执行的上下文保存在栈帧中。栈切换在计算机系统设计中发挥着重要作用,特别是在并发程序中。多进程或多线程程序可以并发执行,充分利用多
CPU 多核的计算资源,从而显著提升应用程序性能。理解进程和线程的切换过程对于优化并发程序至关重要。
操作系统为了避免频繁进入内核态,会将许多工作尽量放在用户态中进行。深入理解内核态和用户态的含义及其影响,有助于评估操作系统进入内核态的开销。实现这些切换的关键在于对执行单元的上下文环境进行切换,而这正是由栈这一核心数据结构支撑的。
掌握协程的基本知识有助于在使用各种协程库时理解其内部机制,进而编写出正确的 IO
程序。同时,深入理解操作系统的用户态和内核态转换,对于架构设计中正确评估操作系统的性能开销也至关重要。在讨论执行单元的切换与栈的关系之前,需要给出它的准确定义。
什么是执行单元
执行单元是指 CPU 调度和分派的基本单位,它是一个 CPU 能正常运行的基本单元。执行单元是可以停下来的,只要能把 CPU
状态(其实就是寄存器的值)全部保存起来,等到这个执行单元再被调度的时候,就把状态恢复过来就行了。我们把这种保存状态,挂起,恢复执行,恢复状态的完整过程,称为执行单元的调度 (
Scheduling)。具体来说,常见的执行单元有进程,线程和协程三种
理解进程和线程
当运行一个可执行程序的时候,操作系统就会启动一个进程。进程会被操作系统管理和调度,被调度到的进程就可以独占 CPU 了。CPU
就像是一个可以轮流使用的工作台,多个进程可以在工作台上工作,时间到了就会带着自己的工作离开工作台,换下一个进程上来工作。进程有自己独立的内存空间和页表,以及文件表等等各种私有资源,如果使用多进程系统,让多个任务并发执行,那么它所占用的资源就会比较多。线程的出现解决了这个问题。同一个进程中的线程则共享该进程的内存空间,文件表,文件描述符等资源,它与同一个进程的其他线程共享资源分配。除了共享的资源,每个线程也有自己的私有空间,这就是线程的栈。线程在执行函数调用的时候,会在自己的线程栈里创建函数栈帧。根据上面所说的特点,人们常把进程看做是资源分配的单位,把线程才看成一个具体的执行实体。
理解协程
协程是比线程更轻量的执行单元。进程和线程的调度是由操作系统负责的,而协程则是由执行单元相互协商进行调度的,所以它的切换发生在用户态。只有前一个协程主动地执行
yield 函数,让出 CPU
的使用权,下一个协程才能得到调度。因为程序自己负责协程的调度,所以大多数时候,我们可以让不那么忙的协程少参与调度,从而提升整个程序的吞吐量,而不是像进程那样,没有繁重任务的进程,也有可能被换进来执行。协程的切换和调度所耗费的资源是最少的,Go
语言把协程和 IO 多路复用结合在一起,提供了非常便捷的 IO 接口,使得协程的概念深入人心。