操作系统中的进程和线程是面试和日常开发中绕不开的话题。很多人能模糊地说出“process 是资源分配单位”“thread 是 CPU 调度单位”,但协程又是什么?它和 thread 到底差在哪?用图解和代码把这三者的关系一次性讲清楚。
操作系统进程
计算机的核心是CPU(中央处理器),CPU承担机器中的所有计算任务;操作系统负责任务调度、资源的分配和管理,IO操作等;进程(Process)是程序的一次动态执行过程,是操作系统进行资源分配和调度的独立单位,也是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。

如上图所示 ,Google Chrome就是一个进程,同理,Tim就是一个进程,Wechat也是一个进程。
进程一般由程序、数据集合和进程控制块三部分组成。
- 程序用于描述进程要完成的功能,是控制进程执行的指令集;
- 数据集合是程序在执行时所需要的数据和工作区;
- 程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志。
进程具有的特征:
- 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
- 并发性:任何进程都可以同其他进程一起并发执行;
- 独立性:进程是系统进行资源分配和调度的一个独立单位;
- 结构性:进程由程序、数据和进程控制块三部分组成。
线程
在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程。
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。
如上图所示,微信是一个进程,下面的Mini Programs是当前进程下的一个线程。
线程有四种基本状态,分别为:
- 产生(spawn)
- 阻塞(block)
- 非阻塞(unblock)
- 结束(finish)
关于线程状态和 IO 阻塞/非阻塞的深入辨析,见 IO 操作同步异步阻塞非阻塞详解。
进程 vs 线程:一句话总结进程、线程、协程
到这里可以先记住两句话:
- Process 是资源分配的最小单位:每个进程有独立的内存空间。一个 process 崩溃不会影响其他进程——Chrome 一个标签页卡死,其他标签页照常
- 线程是 CPU 调度的最小单位:同一进程内的线程共享内存,切换开销远小于进程切换。但一个 thread 挂了可能拖垮整个 process
如果你用过任务管理器,会发现一个应用(进程)下面挂着几十个线程——那就是这些 thread 在工作。
协程
协程,英文Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做用户空间线程,具有对内核来说不可见的特性。因为是自主开辟的异步任务,所以很多人也更喜欢叫它们纤程(Fiber)(Fiber),或者绿色线程(GreenThread)。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。
协程是一个有多个返回点的函数。一般来说一个函数只有一个返回点,函数的调用者调用一次后,函数的生命周期就结束了。而对于协程来说,其生命周期由调用者来决定,可以通过返回值来决定如何进行一次调用以及如何结束调用。
由于协程返回的是一系列的值,每一个yield return对应一次返回。用迭代器作为返回类型是比较好的选择,可以简单的认为每一个yield return对于了迭代器中的一个元素。
Unity的C#代码中一个协程的返回值通常是IEnumerator类型,IEnumerator 接口有两个方法,分别是 Current 和 MoveNext。
我们可以简单的认为:协程就是一个返回迭代器的函数,一开始迭代器的Current指向函数的开头,每执行一次MoveNext,Current就指向下一个yield return 返回的值。
如上图,程序执行过程,协程执行过程。
协程主要功能可以概括为两点:
1.分割程序功能
2.提高运行效率
相关阅读:IO 同步异步阻塞非阻塞 · Docker 入门