[python]异步编程

95 阅读3分钟

参考网址 juejin.cn/post/745153…

juejin.cn/post/739819…

感谢大佬分享

除了多进程,多线程之后, 还有异步编程可以提升性能.

前言

在 Python 3.8 以后的版本中,异步编程变得越来越重要。本文将系统介绍 Python 标准库中的异步编程工具,带领大家掌握 async/await 语法和 asyncio 的使用。

1/从一个简单的场景开始

import time
import random

def process_item(item):
    # 模拟耗时操作
    print(f"处理中:{item}")
    process_time = random.uniform(0.5, 2.0)
    time.sleep(process_time)
    return f"处理完成:{item},耗时 {process_time:.2f} 秒"

def process_all_items():
    items = ["任务A", "任务B", "任务C", "任务D"]
    results = []
    for item in items:
        result = process_item(item)
        results.append(result)
    return results

if __name__ == "__main__":
    start = time.time()
    results = process_all_items()
    end = time.time()
    
    print("\n".join(results))
    print(f"总耗时:{end - start:.2f} 秒")
    
# 输出结果

处理中:任务A
处理中:任务B
处理中:任务C
处理中:任务D
处理完成:任务A,耗时 1.97 秒
处理完成:任务B,耗时 1.28 秒
处理完成:任务C,耗时 0.66 秒
处理完成:任务D,耗时 1.80 秒
总耗时:5.72

这段代码的问题很明显:每个任务都必须等待前一个任务完成才能开始。如果有4个任务,每个任务平均耗时1秒,那么总耗时就接近4秒。

也就是说, 这些任务之间是串行的, 完成一个之后再去执行另一个. 如果任务很多,那么就会很耗时.

2/认识async/ await

Python 引入了 async/await 语法来支持异步编程。

当我们在函数定义前加上 async 关键字时,这个函数就变成了一个"协程"(coroutine)。

await 关键字则用于等待一个协程完成。让我们改写上面的代码:

import asyncio
import random
import time

async def process_item(item):
    print(f"处理中:{item}")
    # async 定义的函数变成了协程
    process_time = random.uniform(0.5, 2.0)
    # time.sleep() 换成 asyncio.sleep()
    await asyncio.sleep(process_time)  # await 等待异步操作完成
    return f"处理完成:{item},耗时 {process_time:.2f} 秒"

async def process_all_items():
    items = ["任务A", "任务B", "任务C", "任务D"]
    # 创建任务列表
    tasks = [
        asyncio.create_task(process_item(item))
        for item in items
    ]
    print("开始处理")
    results = await asyncio.gather(*tasks) # 并发运行多个任务, 并等待它们全部完成
    return results

async def main():
    start = time.time()
    results = await process_all_items()
    end = time.time()
    
    print("\n".join(results))
    print(f"总耗时:{end - start:.2f} 秒")

if __name__ == "__main__":
    asyncio.run(main())

# 输出
开始处理
处理中:任务A
处理中:任务B
处理中:任务C
处理中:任务D
处理完成:任务A,耗时 1.97 秒
处理完成:任务B,耗时 0.80 秒
处理完成:任务C,耗时 0.83 秒
处理完成:任务D,耗时 1.46 秒
总耗时:1.97

可以看到,这里的总耗时只有1.97秒, 是其中耗时最大的一个子任务的执行时间.

让我们详细解释这段代码的执行过程:

  1. 当函数被 async 关键字修饰后,调用该函数不会直接执行函数体,而是返回一个协程对象
  2. await 关键字只能在 async 函数内使用,它表示"等待这个操作完成后再继续"
  3. asyncio.create_task() 将协程包装成一个任务,该任务会被事件循环调度执行
  4. asyncio.gather() 并发运行多个任务,并等待它们全部完成
  5. asyncio.run() 创建事件循环,运行 main() 协程,直到它完成

3/使用 asyncio.wait_for 添加超时控制

在实际应用中,我们往往需要为异步操作设置超时时间:

import asyncio
import random
import time

async def process_item(item):
    process_time = random.uniform(0.5, 2.0)
    try:
        # 设置1秒超时
        await asyncio.wait_for(
            asyncio.sleep(process_time),
            timeout=1.0
        )
        return f"处理完成:{item},耗时 {process_time:.2f} 秒"
    except asyncio.TimeoutError:
        return f"处理超时:{item}"

async def main():
    items = ["任务A", "任务B", "任务C", "任务D"]
    tasks = [
        asyncio.create_task(process_item(item))
        for item in items
    ]
    
    start = time.time()
    results = await asyncio.gather(*tasks, return_exceptions=True)
    end = time.time()
    
    print("\n".join(results))
    print(f"总耗时:{end - start:.2f} 秒")

if __name__ == "__main__":
    asyncio.run(main())
    
# 输出
处理超时:任务A
处理完成:任务B,耗时 0.94 秒
处理超时:任务C
处理完成:任务D,耗时 0.78 秒
总耗时:1.00