for循环里面有异步操作_异步编程 101:asyncio 进阶上篇

本文基于PyCon 2019的一场技术分享,由Spotify工程师探讨asyncio的高级用法。通过一个案例,解释了在for循环中正确使用异步操作的重要性,如避免阻塞事件循环,以及如何实现并发和有序执行任务。文章强调了asyncio中async/await的使用,展示了如何在确保任务顺序的同时提高并发性能,同时提供了优雅关闭程序的方法。
摘要由CSDN通过智能技术生成

9b9dcaee58b2049fe27bbf852a8773d0.png

写在前面:

  • 视频原文:Advanced asyncio: Solving Real-world Production Problems - PyCon 2019
  • slides 链接:https://speakerdeck.com/roguelynn/advanced-asyncio-solving-real-world-production-problems

asyncio 初学者可能较难理解,可以结合我前面的几篇文章一起食用:

廖长江:异步编程 101: 是什么、小试Python asyncio​zhuanlan.zhihu.com
c2aff7ea2a2b93ad1dcebba961c15364.png
廖长江:异步编程 101:Python async await发展简史​zhuanlan.zhihu.com
c2aff7ea2a2b93ad1dcebba961c15364.png
廖长江:异步编程 101:写一个事件循环​zhuanlan.zhihu.com
c2aff7ea2a2b93ad1dcebba961c15364.png
廖长江:异步编程 101:asyncio中的 for 循环​zhuanlan.zhihu.com
c2aff7ea2a2b93ad1dcebba961c15364.png

0x01 本文简介
原视频为 PyCon2019上的一场技术分享,作者是 Spotify的工程师, 通过一个案例, 介绍了 asyncio 的一些 best practice。0x02 初始化 setupconcurrently publish messages
并发地 publish message:注意图中高亮的那一段,这里用的不是await queue.put(msg),这是因为await会阻塞while循环(参考前面的一篇文章:异步编程 101:asyncio中的 for 循环),也就是说,要等queue.put(msg)完成了,下一趟才会开始。而asyncio.create_task()会马上 "fire" 并且立即返回,你可以理解为fire and forget machinism
如果你没时间看异步编程 101:asyncio中的 for 循环,我简要回顾一下:await做的事情是把当前的协程挂起,把控制权交给事件循环,以便于事件循环有其他协程可以调度时,接着运行其他协程。但是对于执行await的这个协程而言,它是被阻塞的。这个例子中,publish()中的while循环是一个整体。

0fcf725975f952dff5c0c1f9c73f2d24.png

concurrently consume messages
这里使用msg = await queue.get()是make sense 的,因为你得先得到 message 然后才能接着做其他事情。而后面的restart_host则用create_task,因为我们不想对他await(等待它完成)而阻塞了整个 while Ture循环。

167fdcd811ad1f747f2673246aafe9b4.png

concurrent work
收到 message 之后,除了restart_host()之外,我们可能还需要做一些其他的任务,比如持久化保存message

2320d35cbffe08a4b4a2c80d3875f466.png

这只需要在consume方法里面再添加一个create_task()

50b8dd03b396aa38c4626e028c5753c1.png

block when needed
然而有时候我们是希望异步任务能够serial执行的。如果要把restart_host()的逻辑改一下:先获取上次重启时间,然后判断上次重启时间是不是大于7天,如果是,再 restart_host()。这里的last_restart_date()restart_host()是有明确先后顺序的。

4d50926d75cc0b94736f378e10807205.png

但是我们又不想这里的线性执行影响后面的 message 获取,很简单,只要把这个逻辑封装成一个协程,然后create_task()就行。0x03 cleanup
需要对message ack,这样producer才不会重新发送。所以现在处理消息的逻辑如下:

7d31048c1a7d980a9bba4cd0163c1611.png

需要保证:saverestart_host全部完成之前,才能cleanup
使用await是能够 work 的,但是性能肯定不够。

61ed10006d6fab0e57230d42cefa7ef7.png

所以asyncio.gather()就派上用场了:这里把save()restart_host()两个协程交给gather,并传给它一个callback,等两个任务全部完成之后调用callback函数,也就是cleanup()

8d2421ab600194daf98bcbc556beab90.png

如果不想用 callback,也可以直接await gather,这样的 code 更加 clean:

2050006f75777e909e557ae50c9b5e11.png

最后把程序跑一下,图中不同颜色表示的是同一个 message:

  • 获取 message 是没有阻塞的
  • saverestart_host全部结束之后,才ack message。

dc3f2b6da18a2cd0d5564d22da71a454.png

0x04 graceful shutdowns
publishconsume组合起来,得到最后的main()

9f52c6b01106828b275d1eafd5e736d6.png

0x05 总结一下

  1. Asynchronous != concurrent

不是说你在原来代码的基础上加上asyncawait就能获得并发性能的,很可能你的异步代码还是 sequantial 的。

  1. Serial != blocking

一些有先后顺序的操作,不意味着就一定是要 block 的。比如先做A 在做 B,在等待 A 完成的过程中,我可以抽出时间做 C。放到本文的例子来说,我需要先saverestart_host才能cleanup,但我可以在等待saverestart_host的时候继续做其他事情:把控制权交给主事件循环,接着运行其他协程。

  1. 回顾一下知识点
  • await的作用是什么?
  • asyncio.create_task()
  • asyncio.gather()

ffdab1678348aa13f3d480a23deb0185.png

如果你像我一样真正热爱计算机科学,喜欢研究底层逻辑,欢迎关注我的微信公众号:

f5297f93011f982916dc525f364488c5.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值