1、协程(coroutine)
协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。一个程序可以包含多个协程,可以对比与一个进程包含多个线程,因而下面我们来比较协程和线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。
简而言之:协程是一种用户态的轻量级线程。
假设A、B是同一线程下的两个协程,则这两个协程之间可以任意的切换,最重要的是解决了线程切换的阻塞问题(这也是为什么协程和异步IO通常在一起被提到)。协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。另外,由于不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
2、异步IO(asyncio)
在一个线程中,CPU执行代码的速度极快,然而,一旦遇到IO操作,如读写文件、发送网络数据时,就需要等待IO操作完成,才能继续进行下一步操作。这种情况称为同步IO。
在IO操作的过程中,当前线程被挂起,而其他需要CPU执行的代码就无法被当前线程执行了。
因为一个IO操作就阻塞了当前线程,导致其他代码无法执行,所以我们必须使用多线程或者多进程来并发执行代码,为多个用户服务。每个用户都会分配一个线程,如果遇到IO导致线程被挂起,其他用户的线程不受影响。
多线程和多进程的模型虽然解决了并发问题,但是系统不能无上限地增加线程。由于系统切换线程的开销也很大,所以,一旦线程数量过多,CPU的时间就花在线程切换上了,真正运行代码的时间就少了,结果导致性能严重下降。
由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法。
另一种解决IO问题的方法是异步IO。当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。
3、代码举例
# -*- coding:utf-8 -*-
__auth__ = 'peic'
'''
Python 协程和异步IO
yield 、yield from的使用
asyncio 模块的使用
'''
# -*- 协程 -*-
# 首先需要理解yield
# 函数中有yield语句而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行,类似CPU的中断处理
def consumer():
c_r = ''
while True:
# 此处yield接受调用者发出的参数,通过send进行调用
# c.send(p_n)中n的值通过yield返回,赋值给r_n
# send(value):The value argument becomes the result of the current yield expression.
# consumer通过yield拿到消息,处理,又通过yield把结果传回。拿到的值是send传递的值,赋给c_n,返回的值是c_r
c_n = yield c_r
if not c_n:
return
print('[CONSUMER] Consuming %s...' % c_n)
c_r = '200 OK'
def produce(c):
c.send(None)
p_n = 0
while p_n < 5:
p_n = p_n + 1
print('[PRODUCER] Producing %s...' % p_n)
# 调用send的generator(此处就是c),send语句会将参数(也就是p_n)的值传给这个生成器目前yield表达式的值(c_n)
# 而send表达式的值(也就是传给p_r的值)会是generator的下一个值(next(c_r))
# 通过调用返回的形式,c.send()就完成了函数的调用返回,即执行了一步generator(consumer)然后返回到原函数(produce),在这个过程中,yield起到中断返回作用
p_r = c.send(p_n)
print('[PRODUCER] Consumer return: %s' % p_r)
c.close()
c = consumer()
produce(c)
# -*- 异步IO -*-
import asyncio
import threading
# @asyncio.coroutine把一个generator标记为coroutine类型
@asyncio.coroutine
def sub():
print('sub start: ...')
n = 10
while True:
print('yield start')
# asyncio.sleep()也是一个coroutine类型的generator,所以线程不会中断,而是直接执行下一个循环,等待yield from的返回
# 可以简单的理解为出现yield之后则开启一个协程(类似开启一个新线程),不管这个协程是否执行完毕,继续下一个循环
# 开启新协程后,print('yield start')会因为继续执行循环被立即执行,可以通过打印结果观察
r = yield from asyncio.sleep(1)
n = n - 1
print('---sub: %s, thread:%s' %(n, threading.currentThread()))
if n == 0:
break
@asyncio.coroutine
def add():
print('add start: ...')
n = 10
while True:
print('yield start')
r = yield from asyncio.sleep(2)
n = n + 1
print('+++add: %s, thread:%s' %(n, threading.currentThread()))
if n > 20:
break
# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
tasks = [add(),sub()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
[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
add start: ...
sub start: ...
---sub: 9, thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 11, thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 8, thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 7, thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 12, thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 6, thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 5, thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 13, thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 4, thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 3, thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 14, thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 2, thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 1, thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 15, thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 0, thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 16, thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 17, thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 18, thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 19, thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 20, thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 21, thread:<_MainThread(MainThread, started 140436577416960)>