协程
协程(coroutine)并不是一个系统层面上真实存在的东西,而是由程序员进行创造。
你可以理解为协程是用户态的“线程”,因此协程也被称为“微线程”或者“纤程(Fiber)”。
协程能够做到在单线程下实现多线程的并发操作,这是非常厉害的一点。
既然协程和线程很像,那么它对比线程有什么优势呢?
- 协程和线程一样能够做切换,但是其切换代价远远小于线程,极大的提升了运行效率
- 协程中修改共享数据时不需要为数据加锁,因为协程本身就是一个单线程
协程有2大重要的概念,如下所示:
- 作为用户态线程,它必然存在于内核态线程中,也就是说协程本身是一个非常纯粹的单线程
- 协程最重要的意义就是切换
普通的代码运行总是顺序执行的,而如果我们有某种机制让它能够遇见I/O后进行自动切换执行就非常牛逼了。
例如下面这个例子,普通的运行打印结果是1、2、3、4,而我们如果加上遇见I/O自动切换执行的策略的话它的打印结果就会变成1、3、2、4:
import time
def task_01():
print(1)
time.sleep(1) # I/O操作
print(2)
def task_02():
print(3)
time.sleep(1) # I/O操作
print(4)
task_01()
task_02()
# 1
# 2
# 3
# 4
如果真的实现了上述的功能,那么使用单线程实现并发就变的不是那么遥不可及了。
生成器
我们可以利用生成器函数的yield关键字来实现代码的切换,如下所示:
import time
def task_01():
print(1)
time.sleep(1) # I/O操作
yield # 手动切换
print(2)
def task_02():
print(3)
time.sleep(1) # I/O操作
yield # 手动切换
print(4)
# 创建生成器对象
genObject_01 = task_01()
genObject_02 = task_02()
# 待执行任务列表
task_list = [genObject_01, genObject_02]
# 开启循环
while 1:
# 可执行任务列表
executable_list = task_list.copy()
# 已执行任务列表
completed_list = []
for run_generator in executable_list:
try:
next(run_generator)
except StopIteration as e:
# 如果任务执行完毕,添加到已执行任务列表中
completed_list.append(run_generator)
for end_generator in completed_list:
# 从待执行任务列表、已执行任务列表中删除已完成任务
task_list.remove(end_generator)
executable_list.remove(end_generator)
else:
# 清空已完成任务列表
completed_list.clear()
if not task_list:
break
# 1
# 3
# 2
# 4
就这样一个基础的协程就做好了,但是你可以发现它的编码难度较大。
并且每次遇见I/O操作后都需要我们手动进行切换,十分的不方便。
gevent模块
针对yield协程的缺点,我们可以利用gevent模块,让整个碰见I/O操作就切换的过程变为自动进行。
它是一个第三方模块,所以你需要手动进行安装下载:
$ pip3 install gevent
代码如下所示:
import gevent
import time
from gevent import monkey
# 声明:下面代码碰见I/O操作自动切换
monkey.patch_all()
def task_01():
print(1)
time.sleep(1) # I/O操作,自动切换
print(2)
time.sleep(1) # I/O操作,自动切换
def task_02():
print(3)
time.sleep(1) # I/O操作,自动切换
print(4)
# 创建协程对象
fiberObject_01 = gevent.spawn(task_01, ) # 后面可传递参数
fiberObject_02 = gevent.spawn(task_02, )
# 任务列表
task_list = [fiberObject_01, fiberObject_02]
# 开始执行
gevent.joinall(
task_list
)
# 1
# 3
# 2
# 4
asyncio模块
Python3.4之后,官方提供了asyncio模块来提供对协程的支持。
下面是代码示例:
import asyncio
# 函数头部加上该装饰器,表明该函数是一个协程函数
@asyncio.coroutine
def task_01():
print(1)
yield from asyncio.sleep(1) # I/O操作 自动切换
print(2)
@asyncio.coroutine
def task_02():
print(3)
yield from asyncio.sleep(1) # I/O操作 自动切换
print(4)
# 创建协程对象,并将它包装为期程对象
fiberObject_01 = asyncio.ensure_future(task_01())
fiberObject_02 = asyncio.ensure_future(task_02())
# 任务列表
task_list = [fiberObject_01, fiberObject_02]
# 获取并开启循环
loop = asyncio.get_event_loop()
# 运行任务列表,并等待所有协程任务执行完毕
loop.run_until_complete(asyncio.wait(task_list))
# 1
# 3
# 2
# 4
我们要注意,如果一个协程函数中要调用另一个函数,则必须使用yield from关键字,yield form后面可以运行的对象类型:
- 协程对象
- 期程对象
- task任务对象
另外,如果你想在协程函数中运行一些模块方法,那么一定要保证模块所提供的方法是异步方法,否则协程切换无效。
有关于yield from的使用,请参照Python基础生成器一章节。
async&awit语法
async和awit语法在Python3.5中被支持,它本质和asyncio模块使用没有任何区别,只是简化了语法。
- async:用于定义协程函数,而不再使用@asyncio.coroutine装饰器来进行定义 <