写点什么

Python 之异步编程

  • 2021 年 12 月 17 日
  • 本文字数:2743 字

    阅读完需:约 9 分钟

Python之异步编程

一、异步编程概述


异步编程是一种并发编程的模式,其关注点是通过调度不同任务之间的执行和等待时间,通过减少处理器的闲置时间来达到减少整个程序的执行时间;异步编程跟同步编程模型最大的不同就是其任务的切换,当遇到一个需要等待长时间执行的任务的时候,我们可以切换到其他的任务执行;


与多线程和多进程编程模型相比,异步编程只是在同一个线程之内的的任务调度,无法充分利用多核 CPU 的优势,所以特别适合 IO 阻塞性任务;


python 版本 3.9.5


二、python 的异步框架模型


python 提供了 asyncio 模块来支持异步编程,其中涉及到 coroutines、event loops、futures 三个重要概念;


event loops 主要负责跟踪和调度所有异步任务,编排具体的某个时间点执行的任务;


coroutines 是对具体执行任务的封装,是一个可以在执行中暂停并切换到 event loops 执行流程的特殊类型的函数;其一般还需要创建 task 才能被 event loops 调度;


futures 负责承载 coroutines 的执行结果,其随着任务在 event loops 中的初始化而创建,并随着任务的执行来记录任务的执行状态;


异步编程框架的整个执行过程涉及三者的紧密协作;


首先 event loops 启动之后,会从任务队列获取第一个要执行的 coroutine,并随之创建对应 task 和 future;


然后随着 task 的执行,当遇到 coroutine 内部需要切换任务的地方,task 的执行就会暂停并释放执行线程给 event loop,event loop 接着会获取下一个待执行的 coroutine,并进行相关的初始化之后,执行这个 task;


随着 event loop 执行完队列中的最后一个 coroutine 才会切换到第一个 coroutine;


随着 task 的执行结束,event loops 会将 task 清除出队列,对应的执行结果会同步到 future 中,这个过程会持续到所有的 task 执行结束;


三、顺序执行多个可重叠的任务


每个任务执行中间会暂停给定的时间,循序执行的时间就是每个任务执行的时间加和;


import time


def count_down(name, delay):indents = (ord(name) - ord('A')) * '\t'


n = 3while n:    time.sleep(delay)    duration = time.perf_counter() - start    print('-' * 40)    print(f'{duration:.4f} \t{indents}{name} = {n}')    n -= 1
复制代码


start = time.perf_counter()


count_down('A', 1)count_down('B', 0.8)count_down('C', 0.5)print('-' * 40)print('Done')

----------------------------------------

1.0010 A = 3

----------------------------------------

2.0019 A = 2

----------------------------------------

3.0030 A = 1

----------------------------------------

3.8040 B = 3

----------------------------------------

4.6050 B = 2

----------------------------------------

5.4059 B = 1

----------------------------------------

5.9065 C = 3

----------------------------------------

6.4072 C = 2

----------------------------------------

6.9078 C = 1

----------------------------------------

Done

四、异步化同步代码


python 在语法上提供了 async、await 两个关键字来简化将同步代码修改为异步;


async 使用在函数的 def 关键字前边,标记这是一个 coroutine 函数;


await 用在 conroutine 里边,用于标记需要暂停释放执行流程给 event loops;


await 后边的表达式需要返回 waitable 的对象,例如 conroutine、task、future 等;


asyncio 模块主要提供了操作 event loop 的方式;


我们可以通过 async 将 count_down 标记为 coroutine,然后使用 await 和 asyncio.sleep 来实现异步的暂停,从而将控制权交给 event loop;


async def count_down(name, delay, start):indents = (ord(name) - ord('A')) * '\t'


n = 3while n:    await asyncio.sleep(delay)    duration = time.perf_counter() - start    print('-' * 40)    print(f'{duration:.4f} \t{indents}{name} = {n}')    n -= 1
复制代码


我们定义一个异步的 main 方法,主要完成 task 的创建和等待任务执行结束;


async def main():start = time.perf_counter()tasks = [asyncio.create_task(count_down(name,delay,start)) for name, delay in [('A', 1),('B', 0.8),('C', 0.5)]]await asyncio.wait(tasks)print('-' * 40)print('Done')


执行我们可以看到时间已经变为了执行时间最长的任务的时间了;


asyncio.run(main())

----------------------------------------

0.5010 C = 3

----------------------------------------

0.8016 B = 3

----------------------------------------

1.0011 A = 3

----------------------------------------

1.0013 C = 2

----------------------------------------

1.5021 C = 1

----------------------------------------

1.6026 B = 2

----------------------------------------

2.0025 A = 2

----------------------------------------

2.4042 B = 1

----------------------------------------

3.0038 A = 1

----------------------------------------

Done

五、使用多线程克服具体任务的异步限制


异步编程要求具体的任务必须是 coroutine,也就是要求方法是异步的,否则只有任务执行完了,才能将控制权释放给 event loop;


python 中的 concurent.futures 提供了 ThreadPoolExecutor 和 ProcessPoolExecutor,可以直接在异步编程中使用,从而可以在单独的线程或者进程至今任务;


import timeimport asynciofrom concurrent.futures import ThreadPoolExecutor


def count_down(name, delay, start):indents = (ord(name) - ord('A')) * '\t'


n = 3while n:    time.sleep(delay)
duration = time.perf_counter() - start print('-'*40) print(f'{duration:.4f} \t{indents}{name} = {n}') n -=1
复制代码


async def main():start = time.perf_counter()loop = asyncio.get_running_loop()executor = ThreadPoolExecutor(max_workers=3)fs = [loop.run_in_executor(executor, count_down, *args) for args in [('A', 1, start), ('B', 0.8, start), ('C', 0.5, start)]]


await asyncio.wait(fs)print('-'*40)print('Done.')
复制代码


asyncio.run(main())

----------------------------------------

0.5087 C = 3

----------------------------------------

0.8196 B = 3

----------------------------------------

1.0073 A = 3

----------------------------------------

1.0234 C = 2

----------------------------------------

1.5350 C = 1

----------------------------------------

1.6303 B = 2

----------------------------------------

2.0193 A = 2

----------------------------------------

2.4406 B = 1

----------------------------------------

3.0210 A = 1

----------------------------------------

Done.

原文链接:https://www.cnblogs.com/wufengtinghai/p/15700736.html

作者:无风听海

发布于: 2 小时前阅读数: 4
用户头像

需要资料添加小助理vx:bjmsb9923 2021.10.15 加入

爱生活爱编程

评论

发布
暂无评论
Python之异步编程