多进程多线程已经是老生常谈了,协程也在最近几年流行起来。特别是有些新生代的语言在设计之初就支持协程如Go语言,而Python也有协程库gevent
。协程被誉为轻量级线程,它比线程调度消耗更小,并且可以轻松达到数十万的并发。作为开发者虽然大多时候只是调用类库的方法就行了,但是对于它们的区别与联系也是非常有必要了解的。
进程
教科书上最经典的解释:进程是资源分配的最小单位
操作系统中最核心的概念是进程,进程是保存在硬盘上的程序运行以后,会在内存空间里形成一个独立的内存体,这个内存体有自己独立的地址空间,有自己的堆,上级挂靠单位是操作系统。简而言之,当我们要运行程序(例如双击打开QQ),操作系统会以进程为单位,分配系统资源(CPU时间片、内存等资源),进程是操作系统资源分配的最小单位。
线程
教科书上最经典的解释:线程是CPU调度的最小单位
也被称为轻量级进程(Lightweight Process,LWP),线程是操作系统调度(CPU调度)和分派的最小单位,线程的上级挂靠是进程,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但可以访问隶属于进程的资源,并且同一进程的所有线程共享该进程的所有资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
协程
教科书上最经典的解释:协程是线程的一种表现形式
是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。实际上协程的概念比线程还要早,一协程就是一次子程序调用,那么实际上协程就是类函数一样的程序组件,你可以在一个线程里面轻松创建数十万个协程,就像数十万次函数调用一样。举个例子:如程序A调用了子程序B,子程序B调用了子程序C,当子程序C结束了返回子程序B继续执行之后的逻辑,当子程序B运行结束了返回程序A,直到程序A运行结束。但是和子程序相比,协程有挂起的概念,协程可以挂起跳转执行其他协程,合适的时机再跳转回来。我的理解:协程实际上就是在用户态实现了对方法的调度,使得各个方法调用不再是同步调用,而是并发调用。(如果知道线程的抢占调度模型,那么这里理解起来就简单了,在协程中可以把线程当做CPU资源,各个方法需要抢占线程运行自己的代码,在用户态实现了一个类似操作系统的调度程序)
Go协程
也叫goroutine。在进程中创建。它比线程更小,十几个goroutine可能体现在底层就是五六个线程,go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需要极少的栈内存(大概4~5kb),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用,更高效,更轻便。一般情况下,一个普通计算机跑几十个线程就有点负载过大了,但是同样的机器却可以轻松的让成百上千个goroutine进行资源竞争。
应用场景及缺点
进程
应用场景
:密集CPU任务,需要充分使用多核CPU资源(服务器,大量的并行计算)的时候,用多进程。进程之间资源不共享,还可以用到多个CPU,如果使用线程的话,还要考虑到数据的安全性,使用互斥锁。缺点
:多个进程之间通信成本高,切换开销大。
线程
应用场景
:密集I/O任务(网络I/O,磁盘I/O,数据库I/O),且 I/O 请求比较快的话。使用多线程合适。常见的大部分任务都是IO密集型任务,比如Web应用。缺点
:一个时间切片只能运行一个线程,不能做到高并行,可以做到高并发。同样存在较大的切换开销
协程
应用场景
:密集I/O任务,且 I/O 请求比较耗时的话,使用协程。因为函数切换,占用资源少。缺点
:单线程执行,处理密集CPU和本地磁盘IO的时候,性能较低。处理网络I/O性能还是比较高。