海到尽头天作岸,山登绝顶我为峰。
1 前言
之前已经分享了许多篇 Python 数据分析的文章,在本文中将继续分享关于 Python 并发编程的内容。 asyncio 是并发代码编码中重要的类库,使用 async/await 关键字以及其它方法实现并发编程,在 IO 密集型和高并发网络、网络服务、数据爬虫编程中应用十分广泛。
2 核心概念
- Event Loop 事件循环:管理所有的时间,负责携程的跳读和执行。
- Coroutine 协程:使用
async
定义的函数,是异步操作的基础。协程可以在等待I/O操作时挂起,让出控制权给事件循环。可以通过await
暂停执行并等待结果。 - Task 任务:对协程进行封装,对协程进行调度指挥。通常任务是非阻塞的,可以并发运行任务。
- Future 异步任务的最终结果,通常不需要直接创建。
- Awaitable 可等待对象。包括协程、任务和Future等,可以在
await
表达式中使用。
Asyncio
和其它 Python
程序一样是单线程的,它只有一个主线程,但是可以分为多个不同的任务进行执行。这里的任务就是 Future
对象,这些不同的任务 event loop
所控制。event loop
会维护两个任务列表,预备状态(空闲状态的任务,随时准备运行)和等待状态(运行状态的任务,正在等待外部操作的完成,比如IO操作)。
3 基础用法
常用的异步程序代码如下图所示:
python
async def async_base_method():
# 模拟 io 操作
print("异步方法开始")
# 设置超时时间
await asyncio.sleep(3)
print("异步方法结束")
# 传入异步方法,是否采用 debug 模式
asyncio.run(async_base_method(), debug= True)
使用创建异步任务的方式执行和超时的配置如下:
python
# 创建任务进行操作
async def async_base_task():
task1 = asyncio.create_task(async_base_method("d1"))
task2 = asyncio.create_task(async_base_method("d2"))
# 等待任务完成,并且设置抛出异常信息
await asyncio.gather(task1, task2, return_exceptions=True)
# 取消任务处理
task1.cancel()
# 使用shield防止任务被取消
await asyncio.shield(task)
# 配置超时
async def timeout_sample():
try:
# 设置超时时间为 10 秒钟
await asyncio.wait_for(async_base_method("d3"), timeout=10)
# 使用 wait api
tasks = [task1, task2, task3]
# 等待所有任务完成,返回 (完成的任务集, 未完成的任务集)
done, pending = await asyncio.wait(tasks)
except asyncio.TimeoutError as e:
print("time out ", e)
4 异步IO操作
异步的IO操作常用的是文件读写以及网络读写,具体的实例如下所示:
python
import aiofiles
import aiohttp
# 异步任务进行文件读写操作,需要安装类库 aiofiles
async def async_file_io():
# 异步读取文件
async with aiofiles.open('data.txt', 'r') as f:
contents = await f.read()
# 异步写入文件
async with aiofiles.open('output.txt', 'w') as f:
await f.write(contents)
# 异步http请求获取返回结果,这里使用的是 aiohttp,而不是 requests,这是因为 Asyncio 不兼容 requests。
async def http_result(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
# 获取数据结果
async def async_query():
urls = ['https://api.example.com', 'https://api.example.cn']
tasks = [http_result(url) for url in urls]
results = await asyncio.gather(*tasks)
for result in results:
print(result) # 打印最终的结果
5 event_loop 操作
此外,还有 event_loop
相关的操作,如下所示:
python
# 获取一个 event_loop
loop = asyncio.get_event_loop()
try:
# 获取当前的事件循环,执行方法直到方法执行完成
loop.run_until_complete(handle_method())
finally:
loop.close()
6 并发编程-future
Futures
模块位于 concurrent.futures
和 asyncio
中,这个模块带有延迟操作。Futures
会将处于等待状态的任务操作封装起来放到队列中。这些任务的状态可以查询状态或者查询异常状态,也可以等待执行完成后获取结果。
python
# 下载数据
def download_one_image(url):
resp = requests.get(url)
print('read {} from {}'.format(len(resp.content), url))
def download_all_images(address):
# 创建线程池执行任务
with concurrent.futures.ThreadPoolExecutor(thread_name_prefix="app_executor", max_workers=5) as executor:
# 这里的 executor.map 和 python 内置函数类似,需要循环 address 并发调用 download_one_image
executor.map(download_one_image, address)
def download_all_images_future(address):
# 使用线程池进行下载数据,返回 future 对象
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
to_do = []
for node in address:
future = executor.submit(download_one_image, node)
to_do.append(future)
for future in concurrent.futures.as_completed(to_do):
future.result()
if __name__ == '__main__':
urls = ["https://pic.netbian.com/uploads/allimg/250219/233238-173997915843ef.jpg",
"https://pic.netbian.com/uploads/allimg/250304/163813-1741077493fa4e.jpg"]
download_all_images(urls)
7 总结
在本文中主要介绍了 Python
的 Asyncio
和 Futures
的并发编程编程实践,总结了其主页的使用方法。相关代码已经上传至 github
, 欢迎大家 star
, 项目地址 fund_python。