目录
编辑
前言
二、Async协程使用步骤
1.导入标准库
2.协程
三、协程的应用场景
1. 网络 IO
2. 数据库 IO
3. 文件 IO
4. 异步任务调度
5. Web 服务
6. 设备和串口 IO
7. 队列和管道
总结
介绍:
Python 协程的概念源于生成器(Generator)。但它通过 asyncio
和事件循环,进一步扩展了生成器的功能,从而支持异步非阻塞操作。允许程序在执行过程中暂停(挂起),然后在需要时恢复运行。与传统的线程或进程并发不同,协程是 单线程内的轻量级任务,由程序自行管理调度(事件循环),而非依赖操作系统,主要用于I/O 密集型如数据库查询,文件读写、网络请求。
大白话:
假设你是个厨师,传统线程的做法是,你炒一道菜必须等这道菜全炒完了才能开始下一道,而协程像个聪明的厨师,边等一道菜炖着时,可以顺手切下一道菜的菜,完全不耽误时间。协程的这种“聪明分配时间”的方式,让它成为处理I/O 密集型任务的好帮手,比如数据库查询、文件操作、或者访问网络数据。
目前主流实现方式:py3.5+ [async ,await] 方式,本文也是主要介绍这种方式。
普通函数:顺序执行执行完func1,然后执行func2
def func1():
print("this is func1")
def func2():
print("this is func2")
if __name__ == "__main__":
func1()
func2()
协程函数:
意义:在遇到IO堵塞的时候,不傻傻等待而是选择其他的异步函数去执行,相当于是你小时候边看电视边吃零食。
import asyncio
1、什么是事件循环也就是event_loop,可以理解为死循环,用于检测和执行某些代码
伪代码:
任务列表 = [ 任务1 ,任务2 ,...]
while True:
可执行列表,已完成列表 = 去任务列表中检查所有的任务,将可执行和已完成的任务返回
for 就绪任务 in 可执行的任务列表:
执行就绪任务
for 已完成任务 in 已完成的任务列表:
在任务列表中移除已完成的任务
如果所有的任务都已经完成了 则终止循环
协程函数定义:async def 函数名,协程对象,执行协程函数()得到的协程对象
import asyncio
async def func():
pass
# 返回协程对象,内部函数不会执行,必须使用循环事件调用
result = func()
asyncio.run(result)
# py3.7 引入asyncio.run(协程对象) 等于之前版本的
# loop = asyncio.get_event_loop()
# loop.run_until_complete(result)
等待对应值得到结果之后继续
await后面只能跟可等待对象 such as [ 协程对象,Feature,Task对象 ] 可以跟多个
import asyncio
async def main():
await join_worker()
print("main function")
async def join_worker():
print("123")
if __name__ == "__main__":
asyncio.run(main())
作用:往事件循环中添加任务
封装协程并在后台执行:通过 asyncio.create_task()
或 loop.create_task()
等方式,可以将一个协程封装为一个 Task
对象,使得它在后台运行,且不阻塞主程序的执行。
并发执行:Task
对象使得多个协程可以并行执行,而无需手动管理协程的调度。它允许我们在事件循环中启动多个协程任务,并等待它们完成。
获取结果:Task
对象可以用于等待协程的执行结果。当我们通过 await task
或 task.result()
获取结果时,Task
会返回协程的返回值。
取消任务:通过 task.cancel()
,可以取消一个正在执行的任务。如果任务已经完成或取消,调用 task.cancel()
不会抛出异常。
处理异常:如果协程抛出异常,Task
对象可以捕获这个异常,允许在后续处理或显示错误信息。
例子一:用到不多
import asyncio
async def func():
print(1)
await asyncio.sleep(2)
print(2)
return "返回值"
async def main():
task1 = asyncio.create_task(func())
task2 = asyncio.create_task(func())
# 放入事件循环中但是并不执行
print("mian 函数结束")
ret1 = await task1
ret2 = await task2
print(ret1, ret2)
asyncio.run(main=main())
# > python async_main.py
# mian 函数结束
# 1
# 1
# 2
# 2
# 返回值 返回值
例子二:用到比较多
import asyncio
async def func():
print(1)
await asyncio.sleep(2)
print(2)
return "返回值"
async def main():
print("函数开始")
await asyncio.gather(func(),func())
asyncio.run(main=main())
# > python async_main.py
# mian 函数结束
# 1
# 1
# 2
# 2
# 返回值 返回值
(基本用不到,理解即可)
Feature
对象(实际上是 concurrent.futures.Future
或 asyncio.Future
)是一个用于表示异步操作结果的容器。它的主要作用是提供一个可以在未来获取异步操作结果的机制,常用于任务管理和协作。
import asyncio
async def set_future_result(future: asyncio.Future):
print("正在设置 Future 的结果...")
await asyncio.sleep(2) # 模拟异步操作
future.set_result("操作成功!") # 设置结果
async def main():
# 创建一个 Future 对象
future = asyncio.Future()
# 启动任务
asyncio.create_task(set_future_result(future))
# 等待 Future 完成并获取结果
result = await future
print(f"Future 结果: {result}")
asyncio.run(main())
在 Python 中,进程池和线程池是用来并发执行任务的两种工具,它们都能显著提升程序在 CPU 密集型或 I/O 密集型任务中的效率。
from multiprocessing import Pool
import os
def task(n):
print(f"Process ID: {os.getpid()} -> Task {n}")
return n * n
if __name__ == "__main__":
with Pool(processes=4) as pool: # 创建一个拥有 4 个进程的进程池
results = pool.map(task, range(10)) # 并发运行任务
print("Results:", results)
Process ID: 12345 -> Task 0
Process ID: 12346 -> Task 1
...
Results: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
使用 concurrent.futures.ThreadPoolExecutor
创建线程池。
from concurrent.futures import ThreadPoolExecutor
import threading
def task(n):
print(f"Thread ID: {threading.get_ident()} -> Task {n}")
return n * n
if __name__ == "__main__":
with ThreadPoolExecutor(max_workers=4) as executor: # 创建一个拥有 4 个线程的线程池
results = list(executor.map(task, range(10))) # 并发运行任务
print("Results:", results)
Thread ID: 140700236138240 -> Task 0
Thread ID: 140700227745536 -> Task 1
...
Results: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
特性 | 进程池 | 线程池 |
---|---|---|
资源使用 | 每个进程有独立内存,消耗更多资源。 | 所有线程共享内存,资源消耗较少。 |
适用任务类型 | CPU 密集型任务(计算、大数据处理)。 | I/O 密集型任务(网络、文件操作)。 |
GIL 影响 | 不受 GIL 影响,多核 CPU 能完全利用。 | 受 GIL 影响,同一时刻只能有一个线程执行字节码。 |
切换开销 | 进程间上下文切换开销较大。 | 线程间上下文切换开销较小。 |
状态共享 | 进程间不共享状态。 | 线程间可以共享状态。 |
错误隔离 | 进程崩溃不会影响其他进程。 | 一个线程崩溃可能影响整个进程。 |
代码可能会存在交叉事件,比如项目是基于协程异步编程的,但是mysql一些版本不支持async,支持进程和线程编程
用法场景,异步编程场景下,第三方模块不支持基于协程的异步,用run_in_executor来解决问题
例子
import time
import concurrent.futures
import asyncio
# 定义一个普通的同步函数,用于模拟耗时操作
def func1():
time.sleep(1) # 模拟任务运行1秒
return "普通函数" # 返回结果
# 定义主异步函数
async def main():
# 获取当前事件循环(Asyncio 的核心,用于管理任务的执行)
loop = asyncio.get_running_loop()
# 使用默认线程池(事件循环自带的线程池)执行同步函数
# run_in_executor 方法将同步函数 func1 放入线程池执行
fut = loop.run_in_executor(None, func1) # None 表示使用默认线程池
# 等待线程池中的任务完成并获取结果
result = await fut
print("默认的池子:", result) # 输出结果
# 显式创建一个线程池执行器(自定义线程池)
with concurrent.futures.ThreadPoolExecutor() as pool:
# 使用自定义线程池执行同步函数 func1
# 将线程池 pool 传递给 run_in_executor 方法
result = await loop.run_in_executor(pool, func=func1)
print("线程池子", result) # 输出线程池执行结果
# 显式创建一个进程池执行器(自定义进程池)
with concurrent.futures.ProcessPoolExecutor() as pool:
# 使用自定义进程池执行同步函数 func1
# 将进程池 pool 传递给 run_in_executor 方法
result = await loop.run_in_executor(pool, func1)
print("进程池子", result) # 输出进程池执行结果
# 使用 asyncio.run() 运行顶级异步函数 main
# asyncio.run 会初始化事件循环并执行 main 函数
asyncio.run(main=main())
实战案例:
import asyncio
import requests
# 定义一个异步函数,用于从 URL 下载图片
async def get_jpg(url: str):
# 获取当前事件循环
loop: asyncio.AbstractEventLoop = asyncio.get_running_loop()
# 在默认线程池中执行 requests.get 请求
result: asyncio.Future[requests.Response] = loop.run_in_executor(
None, requests.get, url
)
# 等待结果完成
response = await result
# 从 URL 提取文件名并保存图片
url_name = url.split("/")[-1]
with open(f"{url_name}.jpg", mode="wb") as file:
file.write(response.content)
# 返回下载的文件名,避免返回 None
return url_name
# 定义一个异步函数,用于并发下载多个图片
async def download_images(urls: list):
tasks = [get_jpg(url) for url in urls]
results = await asyncio.gather(*tasks) # 等待所有任务完成
print(f"Downloaded files: {results}")
# 主入口
if __name__ == "__main__":
# 定义图片 URL 列表
urls = [
"https://example.com/image1.jpg",
"https://example.com/image2.jpg",
"https://example.com/image3.jpg",
]
# 使用 asyncio.run 运行主任务
asyncio.run(download_images(urls))
1. 网络 IO
网络 IO 是 Python 异步最常见的应用场景,得益于其非阻塞特性,可以大幅提升高并发处理能力。
aiohttp
(HTTP 客户端/服务器)websockets
(WebSocket 通信)asyncio
的 stream
API(底层 Socket)示例:HTTP 请求并发
import aiohttp
import asyncio
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
urls = ["https://example.com", "https://python.org"]
results = await asyncio.gather(*(fetch(url) for url in urls))
print(results)
asyncio.run(main())
2. 数据库 IO
异步数据库操作能够在等待查询返回时,不阻塞其他任务。
asyncpg
(PostgreSQL)aiomysql
(MySQL)motor
(MongoDB 异步驱动)SQLAlchemy
的异步支持示例:PostgreSQL 查询
import asyncpg
import asyncio
async def query_db():
conn = await asyncpg.connect('postgresql://user:password@localhost/dbname')
rows = await conn.fetch("SELECT * FROM users")
print(rows)
await conn.close()
asyncio.run(query_db())
3. 文件 IO
虽然文件 IO 本质上是阻塞的,但可以通过 aiofiles
实现异步化,从而避免阻塞主协程。
aiofiles
示例:异步读取文件
import aiofiles
import asyncio
async def read_file(file_path):
async with aiofiles.open(file_path, mode='r') as file:
contents = await file.read()
print(contents)
asyncio.run(read_file("example.txt"))
4. 异步任务调度
协程可以用于处理 IO 密集型任务,同时支持定时任务和多任务调度。
asyncio
示例:多任务调度
import asyncio
async def task(name, delay):
await asyncio.sleep(delay)
print(f"Task {name} completed")
async def main():
await asyncio.gather(
task("A", 2),
task("B", 1),
task("C", 3)
)
asyncio.run(main())
5. Web 服务
异步 Web 框架使得构建高性能 Web 服务变得简单。
FastAPI
Sanic
示例:FastAPI 异步接口
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Hello, Async World!"}
6. 设备和串口 IO
异步串口通信允许与物联网设备进行高效数据交换。
pyserial-asyncio
示例:异步串口读取
import asyncio
import serial_asyncio
async def read_from_device():
reader, writer = await serial_asyncio.open_serial_connection(url='/dev/ttyUSB0', baudrate=9600)
while True:
data = await reader.read(100)
print(f"Received: {data}")
asyncio.run(read_from_device())
7. 队列和管道
异步队列用于实现生产者-消费者模式或任务分发。
asyncio.Queue
示例:生产者-消费者
import asyncio
async def producer(queue):
for i in range(5):
await asyncio.sleep(1)
await queue.put(i)
print(f"Produced: {i}")
async def consumer(queue):
while True:
item = await queue.get()
if item is None:
break
print(f"Consumed: {item}")
queue.task_done()
async def main():
queue = asyncio.Queue()
await asyncio.gather(producer(queue), consumer(queue))
asyncio.run(main())
1、连接mysql异步会导致io请求
pip install aiomysql
import asyncio
import aiomysql
# 异步操作的函数
async def execute():
# 使用 async with 管理连接和游标的生命周期
async with aiomysql.connect(
host="127.0.0.1", port=3306, user="root", password="root", db="pythontest"
) as conn:
# 使用 async with 管理游标
async with conn.cursor() as cur:
# 执行查询操作
await cur.execute("SELECT * FROM student")
# 获取所有查询结果
result = await cur.fetchall()
print("查询结果:", result)
# 如果查询无结果,可以处理默认值或空情况
if not result:
print("没有数据")
# 主程序入口
asyncio.run(execute())
pip install fastapi
pip install uvicorn
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def index():
# 某个io耗时10s 意味着第一个请求等待完之后,第二个请求才能执行
return {"message": "hello world"}
@app.get("/red")
async def red():
# 遇到io请求等待时候马上接第二个请求
return {"message": "async red"}
if __name__ == "__main__":
uvicorn.run(app=app, host="127.0.0.1", port=5000)
import asyncio
import aiohttp
async def fetch_url(url: str):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
# 获取响应内容
data = await response.text()
print(f"Response from {url}: {data[:100]}...") # 只打印前100个字符
# 主入口函数
async def main():
url = "https://jsonplaceholder.typicode.com/todos/1"
await fetch_url(url)
# 运行异步主函数
asyncio.run(main())
asyncio.run()
asyncio.run()
是一个用来执行异步函数的顶级方法。它会启动一个事件循环,运行给定的协程,并在协程执行完毕后关闭事件循环。
async def
和 await
async def
:定义一个异步函数,这个函数返回一个协程对象,而不是普通的返回值。await
:用于等待一个异步操作的结果。它会暂停协程,直到被 await
的任务完成,随后继续执行。asyncio.create_task()
asyncio.create_task()
用于将协程包装成任务并安排执行。它允许你在事件循环中并发执行多个任务。
asyncio.gather()
asyncio.gather()
用于并发执行多个任务并等待它们的结果。它返回一个包含所有任务结果的列表。
asyncio.sleep()
asyncio.sleep()
是异步的 sleep
函数,它不会阻塞事件循环,而是让出控制权,允许其他任务执行。
asyncio.Event
asyncio.Event()
是一个同步原语,用于在线程或协程之间传递信号。事件初始为“未设置”(not set),通过调用 set()
方法将其设为“已设置”(set),其他协程可以通过 wait()
方法等待该事件的设置。
asyncio.Lock
asyncio.Lock
是一个用于协程同步的锁。它用于防止多个协程同时访问共享资源。
asyncio.Semaphore
asyncio.Semaphore
是一个限制并发访问资源的工具。它维护一个计数器,允许最多 n
个协程同时访问资源。
asyncio.Future
asyncio.Future
是一个表示异步操作的结果的对象,它通常用于一些协程间的通信。协程可以通过 Future
传递信息,并在需要时等待其完成。
asyncio.Queue
asyncio.Queue
是一个异步队列,用于在协程之间传递数据,特别适用于生产者-消费者模式。