Python-异步IO
Python-异步IO
一 异步io的作用
- tornado、fastapi、django3.x、aiohttp都在使用异步
- 使用异步的原因是为了提升性能
二 来源
- asyncio和异步编程都是基于协程来演变的
三 预备知识
1 协程
协程不是计算机提供的,是程序员认为创造出来的。也成为微线程,是一种用户态内的上下文切换技术。就是用一个线程实现代码块的相互切换运行。
实现方法:
-
greenlet,早期模块
-
yield关键字
def func1(): yield 1 yield from func2() # 这里要取值不能直接yield func2(),会返回对象而不是执行后的值 yield 2 def func2(): yield 3 yield 4 f1 = func1() for item in f1: print(item) # 1 # 3 # 4 # 2
def func1(): yield 1 # yield from func2() # 这里要取值不能直接yield func2(),会返回对象而不是执行后的值 yield next(f2) yield 2 def func2(): yield 3 yield next(f1) yield 4 f1 = func1() f2 = func2() for item in f2: print(item)
-
gevent模块
-
asyncio装饰器(py3.4)
import asyncio @asyncio.coroutine def func1(): print(1) yield from asyncio.sleep(2) # 遇到io耗时操作,自动化切换tasks中的其他任务 print(2) @asyncio.coroutine def func2(): print(3) yield from asyncio.sleep(2) # 只能用asuncio中的sleep print(4) tasks = [ asyncio.ensure_future(func1()), asyncio.ensure_future(func2()) ] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) # python会同时随机选择一个函数去执行 # 牛逼之处在于他遇到io阻塞会自动切换,如果放到网络io请求,那么他的影响就很大
-
async/await关键字(3.5)【推荐】
# 实际上功能逻辑与asyncio的功能逻辑是一样的,只是为了更加方便使用,python引入了asunc和await两个关键字 import asyncio async def func1(): print(1) await asyncio.sleep(2) # 遇到io耗时操作,自动化切换tasks中的其他任务 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))
2 协程的意义
在协程中,如果遇到io等待时间,线程不会傻傻等,利用等待时间去干其他事情
以协程的方式运行的异步编程
import requests
import asyncio
import aiohttp
# def download_image(url):
# print("开始下载:", url)
# # 发送网络请求
# response = requests.get(url) # 这里返回的不是协程函数、task或者future对象中的一种,所以不能使用await,即requests不支持协程异步
# print("下载完成")
#
# # 图片保存到本地文件
#
# file_name = url.rsplit("_")[-1]
# with open(file_name, "wb") as f:
# f.write((response.content))
async def fetch(session, url):
print("发送请求:", url)
async with session.get(url, verify_ssl=False) as response:
content = await response.content.read()
file_name = url.rsplit("_")[-1]
with open(file_name, "wb") as f:
f.write(content)
print("下载完成", url)
async def main():
async with aiohttp.ClientSession() as session:
url_list = [
"https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg",
"https://www3.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg",
"https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg"
]
# tasks = [asyncio.create_task(fetch(session, url)) for url in url_list] # 报错:module 'asyncio' has no attribute 'create_task'
tasks = [asyncio.get_event_loop().create_task(fetch(session, url)) for url in url_list]
await asyncio.wait(tasks)
if __name__ == '__main__':
# 报错原因:Python 版本低于 3.7
# asyncio.run(main()) # 报错:AttributeError: module 'asyncio' has no attribute 'run'
asyncio.get_event_loop().run_until_complete(main())
# python 3.7 以前的版本调用异步函数的步骤:
# 1、调用asyncio.get_event_loop()函数获取事件循环loop对象
# 2、通过不同的策略调用loop.run_forever()方法或者loop.run_until_complete()方法执行异步函数
四 异步编程
1 事件循环
事件循环:可以理解为一个死循环,去检测并执行某些代码
# 伪代码
任务列表 = [ 任务1, 任务2, 任务3 ...]
while: True:
可执行的任务列表, 已完成的任务列表 = 去任务列表中检查所有的任务,将“可执行”和“已完成”的任务返回
for 就绪任务 in 可执行的任务列表:
执行已就绪的任务
for 已完成的任务 in 已完成的任务列表:
在任务列表中移除已完成的任务
如果 任务列表 中的任务都已经完成,则终止循环
import asyncio
# 去生成或获取一个事件循环
loop = asyncio.get_event_loop()
# 将任务放到“任务列表”
loop.run_until_complete(任务)
uvloop:uvloop是asyncio的事件循环的替代方案。
pip3 install uvloop # 需要python7版本以上
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
# 在这里编写asyncio的代码,与之前写的代码一直。
# 内部的时间循环自动化会变为uvloop
asyncio.run(...)
注意:一个asgi---->uvicorn内部使用的就是uvloop
2 快速上手
协程函数:定义函数时候async def 函数名
。
协程对象:执行协程函数() 得到的协程对象。
async def func():
pass
result = func() # 注意:执行协程函数创建协程对象,函数内部代码不会执行
import asyncio
async def func():
print("协程函数内部")
result = func()
loop = asyncio.get_event_loop()
loop.run_until_complete(result) # 注意:如果要运行协程函数内部代码,必须将协程对象交给事件循环来处理
# python3.7之后可以使用
# asyncio.run(result)
3 await
await + 可等待的对象(协程对象、Future对象、Task对象)-->IO等待
示例1:
import asyncio
async def func():
print("开始")
response = await asyncio.sleep(2) # 它里面本质是做了一个future对象的封装
print("结束", response)
asyncio.get_event_loop().run_until_complete(func())
示例2:
import asyncio
async def others():
print("开始")
response = await asyncio.sleep(2)
print("结束", response)
return "返回值"
async def func():
print("执行协程函数内部代码")
# 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程
response = await others() # 可以这样获得返回值
print("IO请求结束,结果为:", response)
asyncio.get_event_loop().run_until_complete(func())
示例3:
import asyncio
async def others():
print("开始")
response = await asyncio.sleep(2)
print("结束", response)
return "返回值"
async def func():
print("执行协程函数内部代码")
# 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程
response1 = await others()
print("IO请求结束,结果为:", response1)
response2 = await others()
print("IO请求结束,结果为:", response2)
asyncio.get_event_loop().run_until_complete(func())
await就是等待对象的值,等到结果之后再继续向下走。
4 Task对象
Tasks are used to schedule coroutines concurrently.
When a coroutine is wrapped into a Task with functions likeasyncio.create_task()
the coroutine is automatically scheduled to run soon.
白话:在事件循环中添加多个任务的。
Tasks 用于并发调度协程,通过asyncio.create_task(协程对象)
的方式创建 Task 对象,这样可以让协程加入事件循环中等待被调度执行。除了使用 asyncio.create_task()
函数以外,还可以用低层级的loop.create_task()
或 ensure_future()
函数。不建议手动实例化Task对象。
注意:asyncio.create_task()
函数在 Python 3.7 中被加入.在 Python 3.7 之前,可以改用低层级的ensure_future()
函数。
示例1:
import asyncio
async def func(value):
print(value)
await asyncio.sleep(2)
print(value)
return "返回值"
async def main():
print("main开始")
# 创建Task对象,将当前执行func函数任务添加到事件循环
task1 = asyncio.get_event_loop().create_task(func("task1"))
task2 = asyncio.ensure_future(func("task2"))
# task2 = asyncio.create_task(func()) # 版本python3.7或以上
print("main结束")
# 当执行某协程遇到IO操作时,会自动化切换执行其他任务
# 此处的await是等待相对应的协程全部执行完毕并获取结果
ret1 = await task1
ret2 = await task2
print(ret1, ret2)
asyncio.get_event_loop().run_until_complete(main())
示例2:
import asyncio
async def func(value):
print(value)
await asyncio.sleep(2)
print(value)
return "返回值"
async def main():
print("main开始")
# 创建Task对象,将当前执行func函数任务添加到事件循环
# task1 = asyncio.get_event_loop().create_task(func("task1"))
# task2 = asyncio.ensure_future(func("task2"))
# task2 = asyncio.create_task(func()) # 版本python3.7或以上
tasks_list = [
asyncio.get_event_loop().create_task(func("task1")),
asyncio.get_event_loop().create_task(func("task2"))
]
print("main结束")
# 当执行某协程遇到IO操作时,会自动化切换执行其他任务
# 此处的await是等待相对应的协程全部执行完毕并获取结果
# ret1 = await task1
# ret2 = await task2
done, pending = await asyncio.wait(tasks_list, timeout=None)
for _ in range(len(done)):
print(done.pop().result()) # 获取返回值
print(pending)
asyncio.get_event_loop().run_until_complete(main())
示例3:用的比较多
import asyncio
async def func(value):
print(value)
await asyncio.sleep(2)
print(value)
return "返回值"
def main():
print("main开始")
# 创建Task对象,将当前执行func函数任务添加到事件循环
# task1 = asyncio.get_event_loop().create_task(func("task1"))
# task2 = asyncio.ensure_future(func("task2"))
# task2 = asyncio.create_task(func()) # 版本python3.7或以上
tasks_list = [
func("task1"),
func("task2")
]
done, pending = asyncio.get_event_loop().run_until_complete(asyncio.wait(tasks_list))
print("main结束")
# 当执行某协程遇到IO操作时,会自动化切换执行其他任务
# 此处的await是等待相对应的协程全部执行完毕并获取结果
# ret1 = await task1
# ret2 = await task2
# done, pending = await asyncio.wait(tasks_list, timeout=None)
for _ in range(len(done)):
print(done.pop().result()) # 获取返回值
print(pending)
if __name__ == '__main__':
main()
5 asyncio.Future对象
A Future is a special low-level awaitable object that represents an eventual result of an asnchronous operation
Task继承Future,Task对象内部await结果的处理基于Future对象来的
示例1:
import asyncio
async def main():
# 获取当前事件循环
loop = asyncio.get_event_loop()
# 创建一个任务(Future对象),这个任务什么都不干
fut = loop.create_future()
# 等待任务最终结果(Future对象),没有结果则会一直等下去
await fut
asyncio.get_event_loop().run_until_complete(main())
# asyncio.run(main())
示例2:
import asyncio
async def set_after(fut):
await asyncio.sleep(2)
fut.set_result("666")
async def main():
# 获取当前事件循环
loop = asyncio.get_event_loop()
# 创建一个任务(Future对象),没绑定任何行为,则这个任务永远不知道什么时候会结束
fut = loop.create_future()
# 创建一个任务(Task独享),绑定了set_after函数,函数内部在2s之后,会给fut赋值。
# 即手动设置future任务的最终结果,那么fut就可以结束了。
await loop.create_task(set_after(fut))
# 等待任务最终结果(Future对象),没有结果则会一直等下去
result = await fut
print(result)
asyncio.get_event_loop().run_until_complete(main())
6 concurrent.futures.Future对象
使用线程池、进程池实现异步操作时用到的对象
import time
from concurrent.futures import Future
from concurrent.futures.thread import ThreadPoolExecutor
from concurrent.futures.process import ProcessPoolExecutor
def func(value):
time.sleep(1)
print(value)
# 创建线程池
pool = ThreadPoolExecutor(max_workers=5)
# 创建进程池
# pool = ProcessPoolExecutor(max_workers=5)
for i in range(10):
fut = pool.submit(func, i)
print(fut)
以后写代码可能会存在交叉使用的时候,例如:crm项目80%都是基于协程异步编程+MySQL(不支持)【线程、进程做异步编程)
import time
import asyncio
import concurrent.futures
def func1():
# 某个耗时操作
time.sleep(2)
return "返回值"
async def main():
loop = asyncio.get_event_loop()
# 1.Run in the default loop's executor(默认ThreadPoolExecutor)
# 第一步:内部会先调用ThreadPoolExecutor的submit方法去线程池中申请一个线程去执行func1函数,并返回一个concurrent.futures.Future对象
# 第二部:调用asyncio.wrap_future将concurrent.futures.Future对象包装为asycio.Future对象。
# 因为concurrent.futures.Futures对象不支持await语法,所以需要包装为asycio.Future对象才能使用
fut = loop.run_in_executor(None, func1)
result = await fut
print("default thread pool", result)
# 2.Run in a custom thread pool:
with concurrent.futures.ThreadPoolExecutor() as thread_pool:
result = await loop.run_in_executor(thread_pool, func1)
print("custom threadpool", result)
# 3.Run in a custom process pool:
with concurrent.futures.ProcessPoolExecutor() as process_pool:
result = await loop.run_in_executor(process_pool, func1)
print("custom process pool", result)
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
# concurrent.futures.process.BrokenProcessPool: A process in the process pool was terminated abruptly while the future was running or pending.
# 多进程concurrent.futures的ProcessPoolExecutor需要在"if name == main"下运行,否则会报错
# 因为当一个子进程诞生后,会重读代码,读到“with ProcessPoolExecutor() as Exe:”时,在此创建了子进程,而多进程模块不允许子进程中再次创建子进程,所以报错
案例:asyncio+不支持异步的模块
import requests
import asyncio
async def download_image(url):
print("开始下载:", url)
loop = asyncio.get_event_loop()
# requests模块默认不支持异步操作,所以就使用线程池来配合实现了
future = loop.run_in_executor(None, requests.get, url)
# 发送网络请求
response = await future
print("下载完成")
# 图片保存到本地文件
file_name = url.rsplit("_")[-1]
with open(file_name, "wb") as f:
f.write((response.content))
if __name__ == '__main__':
url_list = [
"https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg",
"https://www3.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg",
"https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg"
]
tasks = [download_image(url) for url in url_list]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
# 报错原因:Python 版本低于 3.7
# asyncio.run(main()) # 报错:AttributeError: module 'asyncio' has no attribute 'run'
# asyncio.get_event_loop().run_until_complete(main())
7 异步迭代器
-
什么是异步迭代器
实现了__aiter__()和__anext__()方法的对象。__anext__必须返回一个awaitable对象。async_for会处理异步迭代器的__anext__()方法所返回的可等待对象、指导其引发一个StopAsyncIteration异常。由PEP 429引入。
-
什么是异步可迭代对象?
可在async_for语句中被使用的对象。必须通过它的__aiter__()方法返回一个asynchronous iterator。由PEP 492引入
示例1:
import asyncio
class Reader(object):
"""
自定义异步迭代器(同时也是异步可迭代对象)
"""
def __init__(self):
self.count = 0
# async def readline(self):
def __aiter__(self):
return self
async def __anext__(self):
await asyncio.sleep(1)
self.count += 1
if self.count == 10:
raise StopAsyncIteration
return self.count
# 必须把async for写入一个协程函数中,不然会报错
async def func():
obj = Reader()
async for item in obj:
print(item)
tasks_list = [func() for _ in range(4)] # 这样就能实现多个io等待的for循环异步迭代
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks_list))
8 异步上下文管理器
此种对象通过定义__aenter__()和__aexit__()方法来对async with语句中的环境进行控制。由PEP 492引入
一般需要打开、关闭的操作都会有模块去实现类似的功能,但是当出现打开、关闭操作的时候,脑海里要有异步实现的概念
示例1:
import asyncio
class AsyncContextManager:
def __init__(self, count):
self.count = count
async def do_something(self):
# 异步操作
return 666
async def __aenter__(self):
# 异步链接数据库
await asyncio.sleep(1)
print(self.count, "打开")
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
# 关闭数据库链接
await asyncio.sleep(1)
print(self.count, "关闭")
# 必须把with写入一个协程函数中,不然会报错
async def func(count):
async with AsyncContextManager(count) as f:
result = await f.do_something()
print(result)
tasks_list = [func(count) for count in range(4)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks_list))
五 案例
1 异步操作redis
在使用python代码操作redis时,链接/操作/断开都是网络IO
pip3 install aioredis
示例1:
import asyncio
import aioredis
async def execute(address):
print("开始执行", address)
redis = await aioredis.create_redis(address, password="redis")
# 网路IO操作:在redis中设置哈值car,内部再设三个键值对,即redis = {car:{ keyf1:1, key2:2, key3:3}}
await redis.hmset_dict("car", key1=1, key2=2, key3=3)
# 网络IO操作,去redis中获取值
result = await redis.hgetall("car", encoding='utf-8')
print(result)
redis.close()
# 网络操作,关闭redis连接
await redis.wait_closed()
print("结束", address)
loop = asyncio.get_event_loop()
loop.run_until_complete(execute("redis://192.168.20.133:6379"))
示例2:
import asyncio
import aioredis
async def execute(address):
print("开始执行", address)
redis = await aioredis.create_redis(address, password="redis")
# 网路IO操作:在redis中设置哈值car,内部再设三个键值对,即redis = {car:{ keyf1:1, key2:2, key3:3}}
await redis.hmset_dict("car", key1=1, key2=2, key3=3)
# 网络IO操作,去redis中获取值
result = await redis.hgetall("car", encoding='utf-8')
print(result)
redis.close()
# 网络操作,关闭redis连接
await redis.wait_closed()
print("结束", address)
task_list = [
execute("redis://192.168.20.133:6379"),
execute("redis://192.168.20.133:6379") # 这里改成第二台redis
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(task_list))
2 异步MySQL
pip3 install aiomysql
import asyncio
import aiomysql
async def execute():
# 网络IO操作:连接数据库
conn = await aiomysql.connect(host="127.0.0.1", port=3306, user="root", password="mysql")
# 网络IO操作:chuangjian CURSOR
cur = await conn.cursor()
# 网络IO操作:执行SQL
await cur.execute("use mysql")
await cur.execute("select Host, User from user")
# 网络IO操作:获取SQL结果
result = await cur.fetchall()
print(result)
# 网络IO,关闭连接
await cur.close()
conn.close()
loop = asyncio.get_event_loop()
loop.run_until_complete(execute())
示例2:
import asyncio
import aiomysql
async def execute(host, password):
# 网络IO操作:连接数据库
conn = await aiomysql.connect(host=host, port=3306, user="root", password=password)
# 网络IO操作:chuangjian CURSOR
cur = await conn.cursor()
# 网络IO操作:执行SQL
await cur.execute("use mysql")
await cur.execute("select Host, User from user")
# 网络IO操作:获取SQL结果
result = await cur.fetchall()
print(result)
# 网络IO,关闭连接
await cur.close()
conn.close()
tasks_list = [
execute("127.0.0.1", "mysql"),
execute("127.0.0.1", "mysql") # 换成别的mysql
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks_list))
3 FastAPI框架
pip3 install fastapi
pip3 install uvicorn(asgi内部基于uvloop)
示例1:
import asyncio
import uvicorn
import aioredis
from fastapi import FastAPI
from aioredis import Redis
import time
app = FastAPI()
# 创建一个redis连接池
REDIS_POOL = aioredis.ConnectionsPool("redis://192.168.20.133:6379", password="redis", minsize=1, maxsize=10)
@app.get("/")
def index():
"""
普通操作接口
:return:
"""
time.sleep(10)
return {"message": "Hello world"}
@app.get("/red")
async def red():
"""
异步操作接口
:return:
"""
print("red请求来了")
await asyncio.sleep(10)
conn = await REDIS_POOL.acquire()
redis = Redis(conn)
# 设置值
await redis.set("chen:2", "salve")
# 读取值
result = await redis.get("chen:2")
print(result)
# 连接归还链接池
REDIS_POOL.release(conn)
return result
if __name__ == '__main__':
uvicorn.run("main:app", host="192.168.3.40", port=5000, log_level="info") # main是启动文件名
4 爬虫
pip3 install aiohttp
import aiohttp
import asyncio
async def fetche(session, url):
print("发送请求:", url)
async with session.get(url, verify_ssl=False) as response:
text = await response.text()
print("得到结果:", url, len(text))
async def main():
async with aiohttp.ClientSession() as session:
url_list = [
"https://python.org",
"https://www.baidu.com",
"https://www.pythonav.com"
]
tasks = [asyncio.get_event_loop().create_task(fetche(session, url)) for url in url_list]
done, pending = await asyncio.wait(tasks)
print(done)
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
六 总结
- 使用异步的原因是为了提升性能,异步IO最大的意义:通过一个线程利用其IO等待时间去做一些其他的事情asyncio和异步编程都是基于协程来演变的
- python中使用async/await关键字可以实现异步编程;功能逻辑与asyncio的功能逻辑是一样的,只是为了更加方便使用,python引入了asunc和await两个关键字
- 异步编程:事件循环可以理解为一个死循环,去检测并执行某些代码;await 后面接可等待的对象(协程对象、Future对象、Task对象)-->IO等待;Tasks 在事件循环中添加多个任务的,用于并发调度协程;Task继承Future,Task对象内部await结果的处理基于Future对象来的,用于接收协程对象的结果
- 在模块不支持协程异步的时候可以使用线程/进程做异步
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)