python教程:使用 async 和 await 协程进行并发编程

python 一直在进行并发编程的优化, 比较熟知的是使用 thread 模块多线程和 multiprocessing 多进程,后来慢慢引入基于 yield 关键字的协程。 而近几个版本,python 对于协程的写法进行了大幅的优化,很多之前的协程写法不被官方推荐了。如果你之前了解过 python 协程,你应该看看最新的用法。

并发、并行、同步和异步

并发指的是 一个 CPU 同时处理多个程序,但是在同一时间点只会处理其中一个。并发的核心是:程序切换。

但是因为程序切换的速度非常快,1 秒钟内可以完全很多次程序切换,肉眼无法感知。

bingfa.jpg

并行指的是多个 CPU 同时处理多个程序,同一时间点可以处理多个。

并行.jpg

同步:执行 IO 操作时,必须等待执行完成才得到返回结果。
异步:执行 IO 操作时,不必等待执行就能得到返回结果。

yibu.jpg

协程,线程和进程的区别

多进程通常利用的是多核 CPU 的优势,同时执行多个计算任务。每个进程有自己独立的内存管理,所以不同进程之间要进行数据通信比较麻烦。

多线程是在一个 cpu 上创建多个子任务,当某一个子任务休息的时候其他任务接着执行。多线程的控制是由 python 自己控制的。 子线程之间的内存是共享的,并不需要额外的数据通信机制。但是线程存在数据同步问题,所以要有锁机制。

协程的实现是在一个线程内实现的,相当于流水线作业。由于线程切换的消耗比较大,所以对于并发编程,可以优先使用协程。

。。。
这是对比图:

协程的基础使用

这是 python 3.7 里面的基础协程用法,现在这种用法已经基本稳定,不太建议使用之前的语法了。

import asyncio import time async def visit_url(url, response_time): """访问 url""" await asyncio.sleep(response_time) return f"访问{url}, 已得到返回结果" start_time = time.perf_counter() task = visit_url('http://wangzhen.com', 2) asyncio.run(task) print(f"消耗时间:{time.perf_counter() - start_time}")

1, 在普通的函数前面加 async 关键字;

2,await 表示在这个地方等待子函数执行完成,再往下执行。(在并发操作中,把程序控制权教给主程序,让他分配其他协程执行。) await 只能在带有 async 关键字的函数中运行。

3, asynico.run() 运行程序

4, 这个程序消耗时间 2s 左右。

增加协程

再添加一个任务:

task2 = visit_url('http://another.com', 3) asynicio.run(task2)

这 2 个程序一共消耗 5s 左右的时间。并没有发挥并发编程的优势。如果是并发编程,这个程序只需要消耗 3s,也就是task2的等待时间。要想使用并发编程形式,需要把上面的代码改一下。

import asyncio import time async def visit_url(url, response_time): """访问 url""" await asyncio.sleep(response_time) return f"访问{url}, 已得到返回结果" async def run_task(): """收集子任务""" task = visit_url('http://wangzhen.com', 2) task_2 = visit_url('http://another', 3) await asyncio.run(task) await asyncio.run(task_2) asyncio.run(run_task()) print(f"消耗时间:{time.perf_counter() - start_time}")

asyncio.gather 会创建 2 个子任务,当出现 await 的时候,程序会在这 2 个子任务之间进行调度。

create_task

创建子任务除了可以用 gather 方法之外,还可以使用 asyncio.create_task 进行创建。

async def run_task(): coro = visit_url('http://wangzhen.com', 2) coro_2 = visit_url('http://another.com', 3) task1 = asyncio.create_task(coro) task2 = asyncio.create_task(coro_2) await task1 await task2 协程的主要使用场景

协程的主要应用场景是 IO 密集型任务,总结几个常见的使用场景:

网络请求,比如爬虫,大量使用 aiohttp

文件读取, aiofile

web 框架, aiohttp, fastapi

数据库查询, asyncpg, databases

进一步学习方向(接下来的文章)

什么时候用协程,什么时候用多线程,什么时候用多进程

future 对象

asyncio 的底层 api

loop

trio 第三方库用法

参考文献

talkpython课程源码

trio

猿人学

realpython

Fear and Awaiting in Async by David Beazley

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyyfsz.html