进程、线程、协程 - jerry

Welcome to Aiiyx !

进程、线程、协程

1、多任务

什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,一边在用浏览器上网,一边在听QQ音乐,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。

现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?

答案:就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

所以真正意义上的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。

并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)

并行:指的是任务数小于等于cpu核数,即任务真的是一起执行的

2、多任务-进程

进程:一个程序运行起来后,代码+用到的资源 称之为进程,它是操作系统分配资源的基本单元。

注意: 一个进程包括二进制镜像文件、虚拟内存、需要访问的内核资源、安全上下文等等,操作系统会为进程分配一个唯一id , linux 系统中使用top 命令可以查看进程信息。

进程的状态

工作中,任务数往往大于cpu的核数,即一定有一些任务正在执行,而另外一些任务在等待cpu进行执行,因此导致了有了不同的状态

3、多任务-线程

线程是程序运行的最小调度单元,线程包含在进程中,它包括虚拟处理器、栈、应用程序状态信息等 , 一个进程至少包含一个线程。多线程进程中,理论上每个线程代表单独的任务,多个任务可以同时执行。

在操作系统中两个重要的虚拟化概念是是虚拟内存虚拟处理器。这两个虚拟化给每个进程一个错觉,就是它们都在独享这个计算机资源。

  • 通过虚拟内存,每个进程可以操作的内存地址空间都被认为是整个内存资源(包括磁盘上的交互内存),然后映射到实际的物理内存上,这样将物理内存访问和应用程序的内存访问隔离开。假如计算机上只有4G内存,你起了10个进程,每个进程都认为自己拥有4G内存的空间可以访问。
  • 虚拟处理器,让进程认为它独占处理器资源,运行过程中不用关心是否和其他进程发生争抢,不必去处理实际的资源分配问题。虚拟处理器模型,可以很方便的在多处理器架构上,让多个进程并行执行。
  • 虚拟内存和进程的概念是直接关联的,一个进程中的多个线程共享同一个虚拟内存空间。虚拟处理器和线程是直接关联的,每一个线程是一个独立的调度单元。

Python的多线程和其他语言还是有很大区别的,原则上讲是假的多线程。 这是GIL的作用

4、多任务-协程

协程,又称微线程,纤程。英文名Coroutine。

协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。 为啥说它是一个执行单元,因为它自带CPU上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。

通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定

协程和线程差异

在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。

5、总结

  • 进程是资源分配的单位
  • 线程是操作系统调度的单位
  • 进程切换需要的资源很最大,效率很低
  • 线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
  • 协程切换任务资源很小,效率高
  • 多进程、多线程根据cpu核数( 不考虑GIL的情况 )不一样可能是并行的,但是协程是在一个线程中 所以是并发
  • 并发的关键是有处理多个任务的能力,不一定要同时。
  • 并行的关键是你有同时处理多个任务的能力。
  • 进程、线程、协程均具有多任务能力

注:“并行”概念是“并发”概念的一个子集。也就是说,你可以编写一个拥有多个线程或者进程的并发程序,但如果没有多核处理器来执行这个程序,那么就不能以并行方式来运行代码。因此,凡是在求解单个问题时涉及多个执行流程的编程模式或者执行行为,都属于并发编程的范畴。

6、附录

1)、进程、线程、协程 的区别:

  1. 进程是真正的多任务即并行;
  2. 线程在不用CPython解释器的情况下,比如自己写C语言去调用C语言或者用Jpython,那么线程就是真的多任务,即并行,协程不可能并行
  3. 进程、线程和协程之间的关系和区别也困扰我一阵子了,最近有一些心得,写一下。
  4. 进程拥有自己独立的堆(存)和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
  5. 线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。
  6. 协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
  7. 进程和其他两个的区别还是很明显的。

2)、协程和线程的区别是:

协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。

打个比方吧,假设有一个操作系统,是单核的,系统上没有其他的程序需要运行,有两个线程 A 和 B ,A 和 B 在单独运行时都需要 10 秒来完成自己的任务,而且任务都是运算操作,A B 之间也没有竞争和共享数据的问题。现在 A B 两个线程并行,操作系统会不停的在 A B 两个线程之间切换,达到一种伪并行的效果,假设切换的频率是每秒一次,切换的成本是 0.1 秒(主要是栈切换),总共需要 20 + 19 * 0.1 = 21.9 秒。如果使用协程的方式,可以先运行协程 A ,A 结束的时候让位给协程 B ,只发生一次切换,总时间是 20 + 1 * 0.1 = 20.1 秒。如果系统是双核的,而且线程是标准线程,那么 A B 两个线程就可以真并行,总时间只需要 10 秒,而协程的方案仍然需要 20.1 秒。

3)、互斥锁会产生死锁的现象,互斥锁能保证逻辑上一个任务先做完,再去做另一个任务,GIL只能保证同一时刻只有一个线程再执行

分享
1 条评论