进程、线程与协程
首先明白并发与并行的区别:
- 并发指的是在某一时间段,有多个程序在同一个CPU上运行,在任意一个时间点,只有一个在运行;
- 并行指的是多个CPU同时处理多个任务,强调的是任务处理的同时性。
举个直观的例子:
- 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行;
- 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发;
- 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
进程
是资源分配的最小单位。 同一时刻执行的进程数不会超过核心数(因为一个进程至少含有一个线程)。
线程
是CPU调度的最小单位。
设置的线程的原因是一个程序需要有很多个任务进行协同工作。举个例子:
用播放器看视频时,视频输出的画面和输出的声音可以认为是两种任务。当拖动进度条时又触发了另一种任务。拖动进度条会导致画面和声音都实时发生变化。如果没有线程的话,由于单一程序是阻塞的,那么可能发生的情况就是:拖动进度条→画面更新→声音更新。你会明显感到画面和声音和进度条不同步。
线程的调度与切换比进程快很多。
协程
协程是更加轻量级的线程, 又称微线程,纤程。英文名Coroutine。一个线程可以包含一个或者多个协程。
协程的一个重要特点是在用户态执行,操作系统并不能感知到协程的存在。
协程的一个重要优点是不需要进行线程切换导致的上下文切换,效率更高。
一个线程所包含的所有协程是不可能同时执行的,它们之间是同步的,这点和多个线程的执行有所区别。
举一个Python中协程的例子:
Python的yield
不但可以返回一个值,它还可以接收调用者发出的参数。
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK'
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)
执行结果为:
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK
整个执行流程的解释如下:
注意到consumer
函数是一个generator
,把一个consumer
传入produce
后:
- 首先调用
c.send(None)
启动生成器; - 然后,一旦生产了东西,通过
c.send(n)
切换到consumer
执行; consumer
通过yield
拿到消息,处理,又通过yield
把结果传回;produce
拿到consumer
处理的结果,继续生产下一条消息;produce
决定不生产了,通过c.close()
关闭consumer
,整个过程结束。
整个流程无锁,由一个线程执行,produce
和consumer
协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
最后套用Donald Knuth的一句话总结协程的特点:
子程序就是协程的一种特例。