Python Asyncio以及Futures并发编程实践

海到尽头天作岸,山登绝顶我为峰。

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.futuresasyncio 中,这个模块带有延迟操作。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 总结

在本文中主要介绍了 PythonAsyncioFutures 的并发编程编程实践,总结了其主页的使用方法。相关代码已经上传至 github, 欢迎大家 star, 项目地址 fund_python

相关推荐
啊哈哈哈哈哈啊哈哈37 分钟前
G9打卡——ACGAN
python·生成对抗网络·gan
ALLSectorSorft39 分钟前
相亲小程序用户注册与登录系统模块搭建
java·大数据·服务器·数据库·python
Livingbody1 小时前
ubuntu25.04完美安装typora免费版教程
后端
阿华的代码王国2 小时前
【Android】RecyclerView实现新闻列表布局(1)适配器使用相关问题
android·xml·java·前端·后端
码农BookSea2 小时前
自研 DSL 神器:万字拆解 ANTLR 4 核心原理与高级应用
java·后端
lovebugs2 小时前
Java并发编程:深入理解volatile与指令重排
java·后端·面试
caisexi2 小时前
Windows批量启动java服务bat脚本
java·windows·python
海奥华22 小时前
操作系统到 Go 运行时的内存管理演进与实现
开发语言·后端·golang
codervibe2 小时前
Spring Boot 服务层泛型抽象与代码复用实战
后端