携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第23天,点击查看活动详
1. 协程
协程不是计算机提供,是程序员人为创造的概念。
协程简而言之就是通过一个线程实现代码块相互切换执行。
协程实现了单线程并发,属于异步编程
实现协程的方式有以下几种方法:
- greenlet gevent
- yield
- asyncio装饰器(python3.4)
- async await关键字(python3.5版本之后,推荐使用方式)
1.1 greenlet实现协程
pip install greenlet
from greenlet import greenlet
def func1():
print(1) # 2 输出1
gr2.switch() # 3 执行func2
print(2) # 6 输入2
gr2.switch() # 7 切换到func2,从上一次执行位置继续向后执行
def func2():
print(3) # 4 输出3
gr1.switch() # 5 切换到func1函数,从上次执行的位置继续向后执行
print(4) # 8 输出4
gr1 = greenlet(func1)
gr2 = greenlet(func2)
gr1.switch() # 1 执行func1函数
1.2 yield关键字实现协程(了解)
def func1():
yield 1
yield from func2()
yield 2
def fucn2():
yield 3
yield 4
f = func1()
for i in f:
print(i) # 1 3 4 2
1.3 asyncio实现协程
遇到IO阻塞,asyncio会自动切换
import asyncio
@asyncio.coroutine # 3.8弃用了
def func1():
print(1)
yield from asyncio.sleep(2) # 遇到IO耗时操作时会自动切换到tasks中的其他任务
print(2)
@asyncio.coroutine
def func2():
print(3)
yield from asyncio.sleep(2)
print(4)
tasks = [
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2()),
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
1.5 async和await实现协程(python3.5以后)
推荐使用
import asyncio
async def func1():
print(1)
await asyncio.sleep(2)
print(2)
async def func2():
print(3)
await asyncio.sleep(2)
print(4)
tasks = [
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2()),
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
1.6 协程的意义
在一个线程中如果遇到IO等待,线程不会等待,利用空闲的时间再去做一些其他事情。
2 异步编程
2.1 事件循环
事件循环可以理解为一个while循环,在这个循环中检测并执行一些代码。
事件循环会检查任务列表,对不同状态的任务进行不同的处理:比如一个任务是可以被执行的,那么就会执行该任务,如果一个任务处于IO状态,就是不可以被执行的状态,那么事件循环就会忽略这个任务,如果一个任务已经被执行完成,这个任务就会在任务列表中被删除,直到所有任务完成,事件循环也会随之结束。
任务列表 = [ task1, task2 task3, ...]
while True:
可执行的任务列表 = 任务列表中检查可以被执行的任务
已完成的任务列表 = 任务列表中检查已经执行完成的任务
for 可执行任务 in 可执行任务列表:
执行任务
for 已完成任务 in 已完成任务列表:
任务列表中删除已完成任务
如果任务列表中所有的任务都完成,就终止循环
import asyncio
# 生成一个事件循环
loop = asyncio.get_event_loop()
# 将任务放到任务列表中,并执行事件循环
loop.run_until_complete(任务)
2.2 快速上手
协程函数:async def 函数函数名()
协程对象:执行 协程函数()
得到协程对象
# 协程函数
async def func():
pass
# 协程对象
xc = func()
注意:执行协程函数创建协程对象时,函数内部代码不会执行
如何运行协程函数中的代码?就需要协程对象和事件循环结合使用。
即如果想要运行协程函数内部代码,必须要将协程对象交给时间循环来处理
async def func():
print("代码执行")
res = func() # 得到协程对象
loop = asyncio.get_event_loop()
loop.run_until_complete(res)
asyncio.run(res) # python3.7以后可以使用一行代码代替上述两行代码,原理就是上面事件循环的两行代码
2.3 await
await
就是等到await
之后的对象执行完成之后再继续向下走,当下一步需要上一步的结果时需要使用。
遇到IO就加上await
,用于遇到IO时挂起当前任务,当前任务挂起之后,事件循环可以去执行其他任务
await
+可等待对象
,可等待的对象包括协程对象、Future、Task对象
import asyncio
async def func1():
print(1)
response = await func2()
print(2)
async def func2():
print(3)
await asyncio.sleep(2)
print(4)
return "func2"
asyncio.run(func1())
2.4 Task对象
在事件循环中添加多个任务,并且实现遇到IO进行任务切换的操作。
Task
用于并发调度协程,通过asyncio.create_task(协程对象)
创建task并且将task加到事件循环中。除了使用create_task
可以创建task对象,也可以使用更加底层的方法loop.create_task协程对象()
或者ensure_future(协程对象)
创建task。
asyncio.create_task(协程对象)
在Python3.7及以后版本可以使用,在3.7版本之前使用ensure_future(协程对象)
创建task对象。
import asyncio
async def func1():
print(1)
await asyncio.sleep(2)
print(2)
async def func2():
print("start")
task1 = asyncio.create_task(func1())
task2 = asyncio.create_task(func1())
print("end")
res1 = await task1
res2 = await task2
print(res1, res2)
asyncio.run(func2())
通常事件循环和多个任务结合使用的时候,会使用如下方式编写代码:
import asyncio
async def func1():
print(1)
await asyncio.sleep(2)
print(2)
async def func2():
print("start")
task_list = [
asyncio.create_task(func1(), name='t1'), # name给任务起一个别名
asyncio.create_task(func1())
]
print("end")
# done是协程对象的返回值集合
done, pending = await asyncio.wait(task_list)
print(res1, res2)
asyncio.run(func2())
还可以使用下述方式:
import asyncio
async def func1():
print(1)
await asyncio.sleep(2)
print(2)
'''
直接这样写会报错,因为create_task是立刻将协程对象添加到事件循环中,但是此时事件循环还未创建
task_list = [
asyncio.create_task(func1(), name='t1'), # name给任务起一个别名
asyncio.create_task(func1())
]
'''
task_list = [
func1(), # name给任务起一个别名
func1()
]
asyncio.run(asyncio.wait(task_list))
2.5 asyncio.Future对象
Task继承了Future,Task对象内部await结果的处理就是基于Future对象
import asyncio
async def func1():
print(1)
loop = asyncio.get_running_loop() # 获取事件循环
fu = loop.create_future() # 创建一个任务对象,这个任务什么都不敢
await fu # 等待任务结果Future对象,如果没有结果会一直等待,表现为卡死
asyncio.run(func1())
import asyncio
async def set_after(fu):
await asyncio.sleep(2)
fu.set_result("666")
async def func1():
print(1)
loop = asyncio.get_running_loop() # 获取事件循环
fu = loop.create_future() # 创建一个任务对象,这个任务什么都不做
await loop.create_task(set_after(fu)) # 手动设置future对象的结果
asyncio.run(func1())
2.6 concurrent.futures.Future对象
线程池的Future对象在某些情况下可能会和asyncio的future对象结合使用,比如某些模块不支持协程异步,如果想要实现异步编程就只能借助线程池或者进程池。
如果想要继续使用事件循环执行协程任务,可以使用loop.run_in_executor
方法将concurrent.futures.Future
对象包装成asyncio.Future
对象。
import time
import async.io
import concurrent.futures
def func1(x):
# 模拟不支持协程操作
print(x+1)
await asyncio.sleep(2)
print(2)
async def main():
loop = asyncio.get_running_loop()
fut = loop.run_in_executor(None, func1, 1) # None表示创建线程池,func1执行的函数,1是函数的参数
'''
1. 首先申请线程池执行func1函数返回concurrent.futures.Future对象
2. 调用asyncio.wrap_future将concurrent.futures.Future对象包装成asyncio.Future对象
'''
res = await fut
asyncio.run(main)
2.7 异步迭代器
异步迭代器:实现了__aiter__
和__anext__
方法
异步可迭代对象:实现了__aiter__
方法并且返回异步迭代器
import asyncio
class Reader:
def __init__(self):
self.count = 0
async def readline(self):
self.count += 1
if self.count == 100:
return None
return self.count
def __aiter__(self):
return self
def __anext__(self):
val = await self.readline()
if not val:
raise StopIteration
return val
async def func():
iteration = Reader()
# async for必须写在协程函数中
async for i in iteration:
print(i)
asyncio.run(func())
2.8 异步上下文管理器
通过定义__aenter__
和__aexit__
方法对async with
语句中的环境进行控制。
import asyncio
class Reader:
def __init__(self):
self.conn = None
def __aenter__(self):
# 模拟异步链接数据库
self.conn = await asyncio.sleep(2)
return self
def __aexit__(self, exc_type, exc_val, exc_tb):
# 异步模拟关闭数据库连接
await asyncio.sleep(1)
async def do_something(self):
# 异步操作数据库
return 666
async def func():
# async with必须写在协程函数中
async with Reader() as f:
res = await f.do_something()
print(res)
asyncio.run(func())
3. uvloop
是asyncio事件循环的替代方案,uvloop的事件循环性能要高于asyncio的事件循环。
uvloop暂时不支持windows环境
pip install uvloop
import asyncio
import uvloop
# 使用uvloop的时间循环代替asyncio中的事件循环
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
# 后续代码不变
asyncio.run(...) # 会事件循环自动变成uvloop
运行fastapi项目的uvicorn
内部使用的就是uvloop
4. 异步操作Redis
在使用Python代码操作redis时,链接、操作、断开都是网络IO。
支持网络IO的异步模块是aioredis
,需要进行安装,使用的版本是1.3.1
,使用最新版的aioredis
没有create_redis_pool
方法
import asyncio
import aioredis
async def main():
redis = await aioredis.create_redis_pool('redis://localhost')
await redis.set('my-key', 'value')
value = await redis.get('my-key', encoding='utf-8')
print(value)
redis.close()
await redis.wait_closed()
asyncio.run(main())