计算机的核心是CPU,它承担了所有的计算任务;

而操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,统领整个计算机硬件;

应用程序则是具有某种功能的程序,程序是运行于操作系统之上的.

1. 并发与并行

1.1. 并发

在操作系统中,某一时间段,几个程序在同一个CPU上运行,但在任意一个时间点上,只有一个程序在CPU上运行.

当有多个线程时,如果系统只有一个CPU,那么CPU不可能真正同时进行多个线程,CPU的运行时间会被划分成若干个时间段,每个时间段分配给各个线程去执行,一个时间段里某个线程运行时,其他线程处于挂起状态,这就是并发.

并发解决了程序排队等待的问题,如果一个程序发生阻塞,其他程序仍然可以正常执行.

1.2. 并行

当操作系统有多个CPU时,一个CPU处理A线程,另一个CPU处理B线程,两个线程互相不抢占CPU资源,可以同时进行,这种方式成为并行.

1.3. 区别

[1] - 并发只是在宏观上给人感觉有多个程序在同时运行,但在实际的单CPU系统中,每一时刻只有一个程序在运行,微观上这些程序是分时交替执行;

[2] - 在多CPU系统中,将这些并发执行的程序分配到不同的CPU上处理,每个CPU用来处理一个程序,这样多个程序便可以实现同时执行.

并发的关键是有处理多个任务的能力,不一定要同时.

并行的关键是有同时处理多个任务的能力.

所以可以认为它们最关键的点就是:是否是同时.

2. 进程Process

进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体.

进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.

进程是一种抽象的概念,从来没有统一的标准定义.

一个进程好比是一个程序,它是资源分配的最小单位. 同一时刻执行的进程数不会超过核心数. 不过如果问单核CPU能否运行多进程?答案又是肯定的. 单核CPU也可以运行多进程,只不过不是同时的,而是极快地在进程间来回切换实现的多进程. 举个简单的例子,就算是十年前的单核CPU的电脑,也可以聊QQ的同时看视频.

电脑中有许多进程需要处于同时开启的状态,而利用CPU在进程间的快速切换,可以实现同时运行多个程序. 而进程切换则意味着需要保留进程切换前的状态,以备切换回去的时候能够继续接着工作. 所以进程拥有自己的地址空间,全局变量,文件描述符,各种硬件等等资源. 操作系统通过调度CPU去执行进程的记录、回复、切换等等.

2.1. 进程的组成

进程一般由程序、数据集合和进程控制块三部分组成.

程序用于描述进程要完成的功能,是控制进程执行的指令集;

数据集合是程序在执行时所需要的数据和工作区;

程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志.

示例如图:

2.2. 进程的特征

  • 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
  • 并发性:任何进程都可以同其他进程一起并发执行;
  • 独立性:进程是系统进行资源分配和调度的一个独立单位;
  • 结构性:进程由程序、数据和进程控制块三部分组成.

操作系统有多个程序运行,那么就有多个进程,如图:

3. 线程Thread

线程有时被称为轻量级进程( Lightweight Process, LWP),是程序执行流的最小单元.

一个标准的线程由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成.

另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位. 线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源.

线程拥有自己独立的栈和共享的堆,共享堆,不共享栈.

线程的切换一般也由操作系统调度.

线程具有5种状态:初始化、可运行、运行中、阻塞、销毁.

对操作系统而言,线程是最小的执行单元,进程是最小的资源管理单元.

无论是进程还是线 程,都是由操作系统所管理的.

一个程序内包含了多种任务. 打个比方,用播放器看视频的时候,视频输出的画面和声音可以认为是两种任务. 当拖动进度条的时候又触发了另外一种任务. 拖动进度条会导致画面和声音都发生变化,如果进程里没有线程的话,那么可能发生的情况就是:

拖动进度条->画面更新->声音更新.

会明显感到画面和声音和进度条不同步.

但是加上了线程之后,线程能够共享进程的大部分资源,并参与CPU的调度. 意味着它能够在进程间进行切换,实现并发,从而反馈到使用上就是拖动进度条的同时,画面和声音都同步了. 所以经常能听到的一个词是多线程,就是把一个程序分成多个任务去跑,让任务更快处理. 不过线程和线程之间由于某些资源是独占的,会导致锁的问题. 例如Python的GIL多线程锁.

4. 任务调度

大部分操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式.

在一个进程中,当一个线程任务执行几毫秒后,会由操作系统的内核(负责管理各个任务)进行调度,通过硬件的计数器中断处理器,让该线程强制暂停并将该线程的寄存器放入内存中,通过查看线程列表决定接下来执行哪一个线程,并从内存中恢复该线程的寄存器,最后恢复该线程的执行,从而去执行下一个任务.

上述过程中,任务执行的那一小段时间叫做时间片,任务正在执行时的状态叫运行状态,被暂停的线程任务状态叫做就绪状态,意为等待下一个属于它的时间片的到来.

这种方式保证了每个线程轮流执行,由于CPU的执行效率非常高,时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在“同时进行”,这也就是所说的并发(别觉得并发有多高深,它的实现很复杂,但它的概念很简单,就是一句话:多个任务同时执行).

多任务运行过程的示意图如下:

5. 进程与线程的区别

[1] - 进程是CPU资源分配的基本单位,线程是独立运行和独立调度的基本单位(CPU上真正运行的是线程).

[2] - 线程的调度与切换比进程快很多.

[3] - 进程是应用程序的一个执行实例,比如,在桌面上双击浏览器图标将会运行一个浏览器. 进程与进程之间是相互独立的.

[4] - 线程是一个控制流程,可以在进程内与其他活跃的线程同时执行.

[5] - 进程可以包含多个线程. 如,开启一个浏览器,操作系统将创建一个进程,并开始执行这个进程的主线程. 每一个线程将独立执行一系列的指令(通常就是一个函数),并且和其他线程并行执行. 然而,同一个进程内的线程可以共享一些地址空间和数据结构.

[6] - 线程也被称作“轻量进程”,它和进程有许多共同点,比如都是可以和其他控制流程同时运行的控制流程,(“控制流程”指的是顺序执行一些机器指令);“轻量”是因为实现一个进程比线程要繁重的多. 不同于进程,多个线程可以共享很多资源,特别是地址空间和数据结构等.

[7] - CPU密集型代码(各种循环处理、计算等等):使用多进程;IO密集型代码(文件处理、网络爬虫等):使用多线程

操作系统-进程-线程:

进程-线程:

6. 协程Coroutine

协程,又称微线程,是一种用户态的轻量级线程.

协程是一种比线程更加轻量级的一种函数. 正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程. 协程不是被操作系统内核所管理的,而是完全由程序所控制的,即在用户态执行. 这样带来的好处是:性能有大幅度的提升,因为不会像线程切换那样消耗资源.

协程不是进程也不是线程,而是一个特殊的函数. 这个函数可以在某个地方被“挂起”,并且可以重新在挂起处外继续运行. 所以说,协程与进程、线程相比并不是一个维度的概念.

一个进程可以包含多个线程,一个线程也可以包含多个协程. 简单来说,在一个线程内可以有多个这样的特殊函数在运行,但是有一点必须明确的是:一个线程中的多个协程的运行是串行的. 如果是多核CPU,那多个进程或一个进程内的多个线程是可以并行运行的. 但是在一个线程内协程却绝对是串行的,无论CPU有多少个核毕竟协程虽然是一个特殊的函数,但仍然是一个函数. 一个线程内可以运行多个函数,但这些函数都是串行运行的. 当一个协程运行时,其他协程必须被挂起.

6.1. 协程的特征

协程拥有自己的寄存器上下文和栈. 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈.

因此,协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置.

协程的优点:

  • 无需线程上下文切换的开销
  • 无需原子操作锁定及同步的开销
  • 方便切换控制流,简化编程模型

高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题. 所以很适合用于高并发处理.

协程的缺点:

  • 无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上. 当然,日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用.
  • 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

6.2. 进程与线程和协程的关系

如图:

6.3. 进程与线程和协程的对比

  • 协程既不是进程也不是线程,协程仅是一个特殊的函数. 协程、进程和线程不是一个维度的.
  • 一个进程可以包含多个线程,一个线程可以包含多个协程. 虽然一个线程内的多个协程可以切换但是这多个协程是串行执行的,某个时刻只能有一个线程在运行,没法利用CPU的多核能力.
  • 协程与进程一样,也存在上下文切换问题.
  • 进程的切换者是操作系统,切换时机是根据操作系统自己的切换策略来决定的,用户是无感的. 进程的切换内容包括页全局目录、内核栈和硬件上下文,切换内容被保存在内存中. 进程切换过程采用的是“从用户态到内核态再到用户态”的方式,切换效率低.
  • 线程的切换者是操作系统,切换时机是根据操作系统自己的切换策略来决定的,用户是无感的. 线程的切换内容包括内核栈和硬件上下文. 线程切换内容被保存在内核栈中. 线程切换过程采用的是“从用户态到内核态再到用户态”的方式,切换效率中等.
  • 协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序来决定的. 协程的切换内容是硬件上下文,切换内存被保存在用自己的变量(用户栈或堆)中. 协程的切换过程只有用户态(即没有陷入内核态),因此切换效率高.

7. 最佳实践

[1] - 线程和协程推荐在IO密集型的任务(比如网络调用)中使用,而在CPU密集型的任务中,表现较差.

[2] - 对于CPU密集型的任务,则需要多个进程,绕开GIL的限制,利用所有可用的CPU核心,提高效率.

[3] - 大并发下的最佳实践就是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能.

参考

[1] - 【面试高频问题】线程、进程、协程

[2] - 进程、线程和协程的关系与区别

[3] - 进程、线程、协程之间的关系

[4] - 对进程、线程和协程的理解以及它们的区别

[5] - python-parallel-programming-cookbook-cn 介绍线程和进程

Last modification:June 10th, 2022 at 03:56 pm