首先明白并发与并行的区别:

  1. 并发指的是在某一时间段,有多个程序在同一个CPU上运行,在任意一个时间点,只有一个在运行;
  2. 并行指的是多个CPU同时处理多个任务,强调的是任务处理的同时性。

举个直观的例子:

  1. 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行;
  2. 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发;
  3. 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

进程

资源分配的最小单位。 同一时刻执行的进程数不会超过核心数(因为一个进程至少含有一个线程)。

线程

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后:

  1. 首先调用c.send(None)启动生成器;
  2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
  3. consumer通过yield拿到消息,处理,又通过yield把结果传回;
  4. produce拿到consumer处理的结果,继续生产下一条消息;
  5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produceconsumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

最后套用Donald Knuth的一句话总结协程的特点:

子程序就是协程的一种特例。

Reference