异步 IO
-
CPU 的速度远快于 IO 的速度,如网络和磁盘等。在一个线程中,CPU执行代码的速度极快,然而碰到 IO 操作,就需要等待 IO 操作完成才能进行下一步操作。这种情况称为同步 IO。在 IO 操作过程中,当前线程被挂起,而其他需要 CPU 执行的代码就无法在当前线程执行了。
-
因为 IO 操作会阻塞当前线程,所以提出了多进程或者多线程来并发执行代码,每个用户分配一个线程,不影响其他用户。虽然多进程或者多线程解决了并发问题,但是数量过多,会导致性能下降。我们需要解决的问题是 CPU 高速执行能力和 IO 设备的龟速不匹配问题,这种方式只是一种方法。
-
另一种方法就是异步 IO,异步 IO 需要一个消息循环,在消息循环中,主线程不断地重复“读取消息-处理消息”这一过程。当遇到 IO 操作时,代码是负责发送 IO 请求,不等待 IO 结果,然后直接结束本轮消息处理,进入下一轮消息处理过程。当 IO 操作完成后,将收到一条“IO 完成”的消息,处理该消息时就可以直接获取 IO 操作结果。
在“发出 IO 请求”到收到“IO 完成”的这段时间里,同步 IO 模型下,主线程只能挂起,但异步 IO 模型下,主线程并没有休息,而是在消息循环中继续处理其他消息。在异步 IO 模型下,一个线程就可以同时处理多个 IO 请求,并且没有切换线程的操作。对于大多数IO 密集型的应用程序,使用异步 IO 将大大提升系统的多任务处理能力。
协程
-
协程又叫微线程,英文名Coroutine,像子程序(又叫函数),但是在执行过程中可以中断,然后去执行别的子程序,这种中断不是函数调用,而是类似于 CPU 中断,如:
def A(): print('1') print('2') print('3') def B(): print('x') print('y') print('z')
如果是协程来执行,可能在执行 A 的过程中去执行 B,但是 A 中是没有调用 B 的,结果可能是:
1
2
x
y
3
z
-
协程有点像多线程,但是比多线程有优势。
- 第一个优势就是执行效率很高,因为子程序切换不是线程切换,而是程序自身控制,所以没有线程切换额开销,数量越多,协程的性能优势越能体现。
- 第二个优势就是不需要多线程的锁机制,因为只是一个线程,不存在读写变量冲突,只需要判断状态就好了,所以执行效率比多线程高很多。
-
协程是一个线程执行,为了利用多核 CPU,最简单的方法就是多进程+协程,即充分利用多核,又充分发挥协程的高效率。
-
Python 对协程的支持是通过 generator 实现的。在 generator 中,我们不但可以通过 for 循环来迭代,还可以不断调用 next() 函数获取由 yield 语句返回的下一个值。但是 Python 的 yield 不但可以返回一个值,它还可以接收调用者发出的参数。
-
传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。如果改用协程,生产者生产消息后,直接通过 yield 跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:
def consumer(): r = '' count = 0 print("启动 generator ") while True: print("消费第 %s 次" %count) n = yield r if not n: return print('[CONSUMER] Consuming %s...' % n) r = '200 OK' count += 1 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) print("一轮的数据生成-消费结束") print("===================") c.close() c = consumer() produce(c)运行结果:
启动 generator 消费第 0 次 [PRODUCER] Producing 1... [CONSUMER] Consuming 1... 消费第 1 次 [PRODUCER] Consumer return: 200 OK 一轮的数据生成-消费结束 =================== [PRODUCER] Producing 2... [CONSUMER] Consuming 2... 消费第 2 次 [PRODUCER] Consumer return: 200 OK 一轮的数据生成-消费结束 =================== [PRODUCER] Producing 3... [CONSUMER] Consuming 3... 消费第 3 次 [PRODUCER] Consumer return: 200 OK 一轮的数据生成-消费结束 =================== [PRODUCER] Producing 4... [CONSUMER] Consuming 4... 消费第 4 次 [PRODUCER] Consumer return: 200 OK 一轮的数据生成-消费结束 =================== [PRODUCER] Producing 5... [CONSUMER] Consuming 5... 消费第 5 次 [PRODUCER] Consumer return: 200 OK 一轮的数据生成-消费结束 ===================注意到 consumer 函数是一个 generator ,把一个 consumer 传入 produce 后:
-
首先调用 c.send(None) 启动生成器;
-
然后,一旦生产了东西,通过 c.send(n) 切换到 consumer 执行;
-
consumer 通过 yield 拿到消息,处理,在下一次 while 循环时通过 yield 把结果 r 传回;
-
produce 拿到 consumer 处理的结果,继续生产下一条消息;
-
produce 决定不生产了,通过 c.close() 关闭 consumer,整个过程结束。
-
整个流程无锁,由一个线程执行,produce 和 consumer 协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
-
本文参考:
廖雪峰: www.liaoxuefeng.com/wiki/101695…
感谢支持 支付宝