协程、多线程、多进程的区别?

并行运算中,经常会提到协程、多线程、多进程,请用形象的语言描述三者差异。

喜欢这个问题 | 分享 | 新建回答

回答

喷火的尾巴

Dec 16, 2019
3 赞

接下来说的未必严谨,但相对比较形象。

首先得说说单线程,以请求一个网页为例,如下图所示,把这类任务抽象成3部分,绿色部分代表请求前以及请求前的相关运算处理工作;白色部分代表请求中,等待远程服务器返回结果;蓝色部分代表得到服务器返回结果,并进行相关处理。

单线程、多进程、多线程、协程的对比

一般来说,白色部分属于网络IO,这块所耗费的时间会比较多。

如上所示,单线程的网络IO部分,是会阻塞程序的运行;发出请求到请求返回的这段时间,该程序会占着CPU的该线程的坑位。(注:所谓的单线程就是一个进程只开一个线程)

上面的单线程程序很慢,未来增加干事的速度,那就多开几个一样的程序进行搞事,这就是所谓的多进程的方式。每个进程依然是单线程阻塞的,但是多开了几个同样的程序,从而达到翻倍增加速度的效果。

除了多进程,还有多线程的方式,线程共享该进程的大部分资源,也会参与CPU的调度,但是线程与线程之间,一般以疯狂互相抢占CPU资源为主,并非那么太有序有节操的做事,而是有资源就干,简而言之,抢占式调度;另外就是在实操中,要记住线程是不安全的,线程会是挂的,经常你争我夺,当然会没有那么稳定;每个线程也具有独立的资源,比如栈、寄存器等;另外线程之间的切换也会造成额外资源的开销。(一个多线程的程序属于一个进程,是一个进程内开多线程。)

协程,又称为微线程,英文名为Coroutine它的本质还是一个单线程,在单线程内实现调度,而不是在CPU层面切换线程,从而避免不必要的开销。每个协程拥有自己的栈和寄存器。从实操中来说,协程主要是把很多IO等待的时间剩下来,利用这段等待时间来做别的事情,这就是所谓的异步非阻塞。从单一工作进程来看,只有协程是利用IO等待时间来做别的事情,因此只有协程是实现了非阻塞,而之前说的多线程、多进程,虽然是实现了并行,看似速度很快,但是都是以消耗系统资源来实现,本质上上面的多线程和多进程还是阻塞的。



多核CPU和多线程、多进程、协程的关系:

多线程依然是一个进程,“一个进程只能用一个核来跑,所以单进程多线程,只能把一个核的内力吸尽。” —— 这句话我没有说错,python的多线程是无法利用多核的,除非使用C进行扩展,正常情况下的python多线程是无法使用多核的。不信,可以自己写个多线程的死循环自己试试~
多进程,可以把多核CPU吸尽。
协程虽然很厉害,对资源的利用最高效,但依然是一个单线程的进程,因此只能利用CPU的那一个核,因为不需要线程的切换,而且还能实现IO非阻塞,所以是对资源利用度最高的一种方式。

协程的本质是单线程;但是可以使用“多进程 + 协程”,这是我最为推崇的一种最佳方案;这种方式可以把多核CPU利用到淋漓尽致。这也就是在并行运算中,go语言非常出彩的地方。



说到协程那就推荐一些:

python的协程(异步)并没有那么的出众,但是不出众,不代表不优秀;只是在语法层面没有JavaScript那种常用的异步回调函数。早在python2时,gevent库就是一个非常好的异步框架,通过gevent库可以轻松实现网络请求的非阻塞(用gevent.monkey非常方便);在python3的标准库中,也提供了asyncio库。

Nodejs的也是基于协程的,因此一个Nodejs程序就很高效,但只能把CPU单核占满。(Nodejs不熟悉,不过应该是这样的。)

Golang的并行最为推崇,Golang也就是Go语言,Go的协程是最为厉害的,不仅仅是协程在调度上的设计,而且还可以利用多核CPU,这是一种炸天级别的存在。当然也并非是一定要通过Golang实现并行,通过并行框架,各类语言都能实现相当优异的并行;但框架的使用成本与Golang的使用成本相比较,这就要具体问题具体对待了。至于强调固定资源的最大性能,肯定是golang在这方面做的更好。反正我大致的意思就是Go语言很好;对于是否使用Go语言就得具体问题具体领域具体对待。



经验:能用单线程解决的就用单线程;若要性能翻倍,首选考虑多进程异步,大不了加硬件配置,能用钱解决的事情就甩钱;真正到长期大量需要使用并行时,考虑使用某种并行框架,或者直接换成go语言来写。并行很酷,但切记,能用最简单的单线程解决的就用单线程;简而至上。