Python 核心技术与实战
景霄
Facebook 资深工程师
78765 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 47 讲
开篇词 (1讲)
Python 核心技术与实战
15
15
1.0x
00:00/00:00
登录|注册

22 | 并发编程之Asyncio

你好,我是景霄。
上节课,我们一起学习了 Python 并发编程的一种实现——多线程。今天这节课,我们继续学习 Python 并发编程的另一种实现方式——Asyncio。不同于协程那章,这节课我们更注重原理的理解。
通过上节课的学习,我们知道,在处理 I/O 操作时,使用多线程与普通的单线程相比,效率得到了极大的提高。你可能会想,既然这样,为什么还需要 Asyncio?
诚然,多线程有诸多优点且应用广泛,但也存在一定的局限性:
比如,多线程运行过程容易被打断,因此有可能出现 race condition 的情况;
再如,线程切换本身存在一定的损耗,线程数不能无限增加,因此,如果你的 I/O 操作非常 heavy,多线程很有可能满足不了高效率、高质量的需求。
正是为了解决这些问题,Asyncio 应运而生。

什么是 Asyncio

Sync VS Async

我们首先来区分一下 Sync(同步)和 Async(异步)的概念。
所谓 Sync,是指操作一个接一个地执行,下一个操作必须等上一个操作完成后才能执行。
而 Async 是指不同操作间可以相互交替执行,如果其中的某个操作被 block 了,程序并不会等待,而是会找出可执行的操作继续执行。
举个简单的例子,你的老板让你做一份这个季度的报表,并且邮件发给他。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Python 核心技术与实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(71)

  • 最新
  • 精选
  • Jingxiao
    置顶
    思考题答案: import multiprocessing import time def cpu_bound(number): return sum(i * i for i in range(number)) def find_sums(numbers): with multiprocessing.Pool() as pool: pool.map(cpu_bound, numbers) if __name__ == "__main__": numbers = [10000000 + x for x in range(20)] start_time = time.time() find_sums(numbers) duration = time.time() - start_time print(f"Duration {duration} seconds")
    7
    28
  • 建强
    上网查询资料后,初步了解了多进程的一些知识,按照资料中的方法简单改写了一下程序,由于多进程方式时,不知什么原因,cpu_bound函数不能实时输出,所以就把cpu_bound改为返回字符串形式的结果,等所有的数计算完成后,再一并输出结果 ,程序中常规执行和多进程两种方式都有,并作了对比后发现,常规执行用时约23秒,多进程用时约6秒,两者相差4倍,程序如下,不足处请老师指正: #多进程演示 import multiprocessing import time def cpu_bound(number): return 'sum({}^2)={}'.format(number,sum(i * i for i in range(number))) def calculate_sums(numbers): results = [] print('-'*10+'串行执行开始:'+'-'*10) for number in numbers: results.append(cpu_bound(number)) print('-'*10+'串行执行结束,结果如下:'+'-'*10) for res in results: print(res) def multicalculate_sums(numbers): #创建有4个进程的进程池 pool = multiprocessing.Pool(processes=4) results = [] print('-'*10+'多进程执行开始:'+'-'*10) #为每一个需要计算的元素创建一个进程 for number in numbers: results.append(pool.apply_async(cpu_bound, (number,))) pool.close() #关闭进程池,不能往进程池添加进程 pool.join() #等待进程池中的所有进程执行完毕 print('-'*10+'多进程执行结束,结果如下:'+'-'*10) for res in results: print(res.get()) def main(): numbers = [10000000 + x for x in range(20)] #串行执行方式 start_time = time.perf_counter() calculate_sums(numbers) end_time = time.perf_counter() print('串行执行用时:Calculation takes {} seconds'.format(end_time - start_time)) #多进程执行方式 start_time = time.perf_counter() multicalculate_sums(numbers) end_time = time.perf_counter() print('多进程执行用时:Calculation takes {} seconds'.format(end_time - start_time)) if __name__ == '__main__': main()

    作者回复: 很棒的例子,但是对计算密集型程序,你可以打开任务管理器的性能页,CPU 选择显示逻辑处理器,可以注意到串行执行和并行执行的不同。

    2
    5
  • 阿卡牛
    常听到阻塞,同步是不是就是阻塞地意思

    作者回复: 通俗来讲是

    2
  • szc
    能否举一些例子,哪些场景是IO密集型中的IOheavy, 那些是IO很快

    作者回复: 这个得看具体场景。比如大公司里相应业务爬虫的规模非常大,要抓取百万级的视频新闻信息流,这种就属于IO heavy。但是如果你只需要抓取10个网站的信息,并且网络连接良好,那么IO就很快

  • helloworld
    总结多线程和协程之间的共同点和区别: 共同点: 都是并发操作,多线程同一时间点只能有一个线程在执行,协程同一时间点只能有一个任务在执行; 不同点: 多线程,是在I/O阻塞时通过切换线程来达到并发的效果,在什么情况下做线程切换是由操作系统来决定的,开发者不用操心,但会造成race condition; 协程,只有一个线程,在I/O阻塞时通过在线程内切换任务来达到并发的效果,在什么情况下做任务切换是开发者决定的,不会有race condition的情况; 多线程的线程切换比协程的任务切换开销更大; 对于开发者而言,多线程并发的代码比协程并发的更容易书写。 一般情况下协程并发的处理效率比多线程并发更高。
    3
    56
  • hlz-123
    1、单进程,老师的原程序,运行时间 Calculation takes 15.305913339 seconds 2、CPU并行方式,运行时间: Calculation takes 3.457259904 seconds def calculate_sums(numbers): with concurrent.futures.ProcessPoolExecutor() as executor: executor.map(cpu_bound,numbers) 3、多线程,cocurrent.futures,运行时间 Calculation takes 15.331446270999999 seconds def calculate_sums(numbers): with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: executor.map(cpu_bound,numbers) 4、异步方式,asyncio Calculation takes 16.019983702999998 seconds async def cpu_bound(number): print(sum(i * i for i in range(number))) async def calculate_sums(numbers): tasks=[asyncio.create_task(cpu_bound(number)) for number in numbers] await asyncio.gather(*tasks)
    4
    30
  • 天凉好个秋
    如果完成,则将其放到预备状态的列表; 如果未完成,则继续放在等待状态的列表。 这里是不是写的有问题? PS:想问一下,完成之后为什么还要放队列里?难道不应该从队列里移除吗?
    2
    8
  • transformation
    import time from concurrent import futures def cpu_bound(number): return sum(i * i for i in range(number)) def calculate_sums(numbers): for number in numbers: print(cpu_bound(number)) def main(): start_time = time.perf_counter() numbers = [10000000 + x for x in range(20)] calculate_sums(numbers) end_time = time.perf_counter() print('Calculation takes {} seconds'.format(end_time - start_time)) def main_process(): start_time = time.perf_counter() numbers = [10000000 + x for x in range(20)] with futures.ProcessPoolExecutor() as pe: result = pe.map(cpu_bound, numbers) print(f"result: {list(result)}") end_time = time.perf_counter() print('multiprocessing Calculation takes {} seconds'.format(end_time - start_time)) if __name__ == '__main__': main() main_process() ———————— 输出: 333333283333335000000 333333383333335000000 333333483333355000001 333333583333395000005 333333683333455000014 333333783333535000030 333333883333635000055 333333983333755000091 333334083333895000140 333334183334055000204 333334283334235000285 333334383334435000385 333334483334655000506 333334583334895000650 333334683335155000819 333334783335435001015 333334883335735001240 333334983336055001496 333335083336395001785 333335183336755002109 Calculation takes 15.771127400000001 seconds result: [333333283333335000000, 333333383333335000000, 333333483333355000001, 333333583333395000005, 333333683333455000014, 333333783333535000030, 333333883333635000055, 333333983333755000091, 333334083333895000140, 333334183334055000204, 333334283334235000285, 333334383334435000385, 333334483334655000506, 333334583334895000650, 333334683335155000819, 333334783335435001015, 333334883335735001240, 333334983336055001496, 333335083336395001785, 333335183336755002109] multiprocessing Calculation takes 4.7333084 seconds
    5
    8
  • Paul Shan
    sync是线性前后执行。 async是穿插执行,之所以要穿插,代码需要的资源不同,有的代码需要CPU,有的代码需要IO(例如网络),穿插以后,同时需要CPU和网络的代码可以同时执行,充分利用硬件。 具体到关键字 async 是表示函数是异步的,也就是来回穿插的起点(进入预备队列),await是表示调用需要IO,也就是进入等待队列的入口(函数开始调用)和出口(函数调用结束,重新进入预备队列)。
    3
  • 唐哥
    老师好,对于 Asyncio 来说,它的任务在运行时不会被外部的一些因素打断。不被打断是如何保证的?还有event loop是每次取出一个任务运行,当这个任务运行期间它就是只等待任务结束吗?不干其他事了吗?
    2
    3
收起评论
显示
设置
留言
71
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部