【转】以Python为例的Async / Await的编程基础

时间:2023-02-07 11:32:34

转, 原文:https://www.cnblogs.com/middleware/p/11996731.html

以Python为例的Async / Await的编程基础

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

来源:Redislabs

作者:Loris Cro

翻译:Kevin (公众号:中间件小哥)

近年来,许多编程语言都在努力改进它们的并发原语。Go 语言有 goroutines,Ruby 有 fibers,当然,还有 Node.js 帮助普及的 async/await,这是当今使用最为广泛的并发操作类型。在本文中,我将以 python 为例讨论 async/await 的基础知识。我选择python语言,是因为这个功能在python 3中比较新,很多用户可能对它还不是很熟悉。使用 async/await 的主要原因是通过减少 I/O 执行时的空闲时间来提高程序的吞吐量。使用这个操作符的程序通过隐式地使用一个称为事件循环的抽象来同时处理多个执行路径。在某些方面,这些事件循环类似于多线程编程,但是事件循环通常存在于单个线程中,因此,它不能同时执行多个计算。正因为如此,单独的事件循环不能提高计算密集型应用程序的性能。但是,对于进行大量网络通信的程序,比如连接到Redis数据库的应用程序,它可以极大地提高性能。每次程序向 Redis 发送一个命令时,它都会等待 Redis 的响应,如果 Redis 部署在另一台机器上,就会出现网络延迟。而一个不使用事件循环的单线程应用程序在等待响应时处于空闲状态,会占用大量的CPU周期。需要注意的是,网络延迟是以毫秒为单位的,而 CPU 指令需要纳秒来执行,这两者相差六个数量级。这里举个例子,下面的代码样例是用来跟踪一个游戏的获胜排行榜。每个流条目都包含获胜者的名字,我们的程序会更新一个 Redis 的有序集合(Sorted Set),这个有序集合用来作为排行榜。这里我们主要关注的是阻塞代码和非阻塞代码的性能。

【转】以Python为例的Async / Await的编程基础
 1 import redis
2
3 # The operation to perform for each event
4 def add_new_win(conn, winner):
5 conn.zincrby('wins_counter', 1, winner)
6 conn.incr('total_games_played')
7
8 def main():
9 # Connect to Redis
10 conn = redis.Redis()
11 # Tail the event stream
12 last_id = '$'
13 while True:
14 events = conn.xread({'wins_stream': last_id}, block=0, count=10)
15 # Process each event by calling `add_new_win`
16 for _, e in events:
17 winner = e['winner']
18 add_new_win(conn, winner)
19 last_id = e['id']
20
21 if __name__ == '__main__':
22 main()
【转】以Python为例的Async / Await的编程基础

  

我们使用aio-libs/aioredis实现与上面代码有相同效果的异步版本。aio-libs 社区正在重写许多 Python 网络库,以包括对 asyncio 的支持,asyncio 是 Python 事件循环的标准库实现。下面是上面代码的非阻塞版本:

【转】以Python为例的Async / Await的编程基础
 1 import asyncio
2 import aioredis
3
4 async def add_new_win(pool, winner):
5 await pool.zincrby('wins_counter', 1, winner)
6 await pool.incr('total_games_played')
7
8 async def main():
9 # Connect to Redis
10 pool = await aioredis.create_redis_pool('redis://localhost', encoding='utf8')
11 # Tail the event stream
12 last_id = '$'
13 while True:
14 events = await pool.xread(['wins_stream'], latest_ids=[last_id], timeout=0, count=10)
15 # Process each event by calling `add_new_win`
16 for _, e_id, e in events:
17 winner = e['winner']
18 await add_new_win(pool, winner)
19 last_id = e_id
20
21 if __name__ == '__main__':
22 loop = asyncio.get_event_loop()
23 loop.run_until_complete(main())
【转】以Python为例的Async / Await的编程基础

这段代码与上面那段代码相比,除了多了一些 await 关键字之外,其他的几乎是相同的。最大的不同之处在最后两行。在 Node.js 中,环境会默认加载事件循环,而在 Python 中,必须显示地开启。
 重写之后,我们可能会认为这么做就可以提高性能了。不幸的是,我们代码的非阻塞版本还没有提高性能。这里的问题在于我们编写代码的细节,而不仅仅是使用 async / await 的一般思想。

Await 使用的限制

我们重写代码后的主要问题是我们过度使用了 await。当我们在异步调用前面加上 await 时,我们做了以下两件事:

1. 为执行做相应的调度

2. 等待完成

有时候,这样做是对的。例如,在完成对第 15 行流的读取之前,我们不能对每个事件进行迭代。在这种情况下,await 关键字是有意义的,但是看看 add_new_win 方法:

1 async def add_new_win(pool, winner):
2 await pool.zincrby('wins_counter', 1, winner)
3 await pool.incr('total_games_played')

在这个函数中,第二个操作并不依赖于第一个操作。我们可以将第二个命令与第一个命令一起发送,但是当我们发送第一个命令时,await 将阻塞执行流。我们其实更想要一种能立即执行这两个操作的方法。为此,我们需要一个不同的同步原语。

1 async def add_new_win(pool, winner):
2 task1 = pool.zincrby('wins_counter', 1, winner)
3 task2 = pool.incr('total_games_played')
4 await asyncio.gather(task1, task2)

首先,调用一个异步函数不会执行其中的任何代码,而是会先实例化一个“任务”。根据选择的语言,这可能被称为 coroutine, promise 或 future 等等。对我们来说,任务是一个对象,它表示一个值,该值只有在使用了 await 或其他同步原语(如 asyncio.gather)之后才可用。 在 Python 的官方文档中,你可以找到更多关于 asyncio.gather 的信息。简而言之,它允许我们在同一时间执行多个任务。我们需要等待它的结果,因为一旦所有的输入任务完成,它就会创建一个新的任务。Python 的 asyncio.gather 相当于 JavaScript 的 Promise.all,C# 的 Task.WhenAll, Kotlin 的 awaitAll 等等。

改进我们的主循环代码

我们对 add_new_win 所做的事情也可以用于主流事件处理循环。这是我所指的代码:

【转】以Python为例的Async / Await的编程基础
1 last_id = '$'
2 while True:
3 events = await pool.xread(['wins_stream'], latest_ids=[last_id], timeout=0, count=10)
4 for _, e_id, e in events:
5 winner = e['winner']
6 await add_new_win(pool, winner)
7 last_id = e_id
【转】以Python为例的Async / Await的编程基础

到目前为止,你会注意到我们是顺序地处理每个事件。因为在第 6 行中,使用 await 既可以执行又可以等待 add_new_win 的完成。有时这正是你希望发生的情况,因为如果你不按顺序执行,程序逻辑就会中断。在我们的例子中,我们并不真正关心排序,因为我们只是更新计数器。

【转】以Python为例的Async / Await的编程基础
1 last_id = '$'
2 while True:
3 events = await pool.xread(['wins_stream'], latest_ids=[last_id], timeout=0, count=10)
4 tasks = []
5 for _, e_id, e in events:
6 winner = e['winner']
7 tasks.append(add_new_win(pool, winner))
8 last_id = e_id
9 await asyncio.gather(*tasks)
【转】以Python为例的Async / Await的编程基础
我们现在也在并发地处理每一批事件,并且对代码的改动是最小的。最后要记住,有时即使不使用 asyncio.gather,程序也可以是高性能的。特别是,当你为 web 服务器编写代码并使用像 Sanic 这样的异步框架时,该框架将以并发的方式调用你的请求处理程序,即使你在等待每个异步函数调用,也能确保巨大的吞吐量。

总结

下面是我们进行上面两个更改之后的完整代码示例:

【转】以Python为例的Async / Await的编程基础
 1 import asyncio
2 import aioredis
3
4 async def add_new_win(pool, winner):
5 # Creating tasks doesn't schedule them
6 # so you can create multiple and then
7 # schedule them all in one go using `gather`
8 task1 = pool.zincrby('wins_counter', 1, winner)
9 task2 = pool.incr('total_games_played')
10 await asyncio.gather(task1, task2)
11
12 async def main():
13 # Connect to Redis
14 pool = await aioredis.create_redis_pool('redis://localhost', encoding='utf8')
15 # Tail the event stream
16 last_id = '$'
17 while True:
18 events = await pool.xread(['wins_stream'], latest_ids=[last_id], timeout=0, count=10)
19 tasks = []
20 for _, e_id, e in events:
21 winner = e['winner']
22 # Again we don't actually schedule any task,
23 # and instead just prepare them
24 tasks.append(add_new_win(pool, winner))
25 last_id = e_id
26 # Notice the spread operator (`*tasks`), it
27 # allows using a single list as multiple arguments
28 # to a function call.
29 await asyncio.gather(*tasks)
30
31 if __name__ == '__main__':
32 loop = asyncio.get_event_loop()
33 loop.run_until_complete(main())
【转】以Python为例的Async / Await的编程基础

为了利用非阻塞 I/O,你需要重新考虑如何处理网络操作。值得高兴的是这并不是很困难,你只需要知道顺序性什么时候重要,什么时候不重要。尝试使用 aioredis 或等效的异步 redis 客户端,看看可以在多大程度上提高应用程序的吞吐量。

【转】以Python为例的Async / Await的编程基础的更多相关文章

  1. 以Python为例的Async / Await的编程基础

    来源:Redislabs 作者:Loris Cro 翻译:Kevin (公众号:中间件小哥) 近年来,许多编程语言都在努力改进它们的并发原语.Go 语言有 goroutines,Ruby 有 fibe ...

  2. [翻译] Python 3.5中async/await的工作机制

    Python 3.5中async/await的工作机制 多处翻译出于自己理解,如有疑惑请参考原文 原文链接 身为Python核心开发组的成员,我对于这门语言的各种细节充满好奇.尽管我很清楚自己不可能对 ...

  3. C#中 Thread,Task,Async/Await 异步编程

    什么是异步 同步和异步主要用于修饰方法.当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法:当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调 ...

  4. 【转】C# Async/Await 异步编程中的最佳做法

    Async/Await 异步编程中的最佳做法 Stephen Cleary 近日来,涌现了许多关于 Microsoft .NET Framework 4.5 中新增了对 async 和 await 支 ...

  5. .NET Web应用中为什么要使用async/await异步编程

    前言 什么是async/await? await和async是.NET Framework4.5框架.C#5.0语法里面出现的技术,目的是用于简化异步编程模型. async和await的关系? asy ...

  6. 图与例解读Async/Await

    JavaScript ES7的async/await语法让异步promise操作起来更方便.如果你需要从多个数据库或者接口按顺序异步获取数据,你可能最终写出一坨纠缠不清的promise与回调.然而使用 ...

  7. async/await 异步编程(转载)

    转载地址:http://www.cnblogs.com/teroy/p/4015461.html 前言 最近在学习Web Api框架的时候接触到了async/await,这个特性是.NET 4.5引入 ...

  8. async/await 异步编程

    前言 最近在学习Web Api框架的时候接触到了async/await,这个特性是.NET 4.5引入的,由于之前对于异步编程不是很了解,所以花费了一些时间学习一下相关的知识,并整理成这篇博客,如果在 ...

  9. async & await 异步编程的一点巧方法

    await 关键字不会创建新的线程,而是由Task任务或是FCL中的xxxAsync等方法创建的线程,而且这里创建的线程都是基于线程池创建的工作线程,属于后台线程. await关键字会阻塞/暂停调用它 ...

随机推荐

  1. Xcode 快捷键、常用技巧

    关于iOS开发中的技能快捷键 经常使用鼠标太TM的D疼了,快捷键能大大地提高我们的开发速度,使我们的手指尽情的在键盘上飞舞,优美的代码,哈哈哈,那些常规的复制.粘贴.剪切请自行度娘或者Google一下 ...

  2. 申请使用aws的一些笔记

    1. 申请可以使用asw.amazon.com/cn/,这个界面虽然是中文的,但是申请的是海外的aws. 2. 审核后会收到如下的一封邮件: 3. 剩下创建EC2和RDS的过程可以参考http://w ...

  3. Java 对象销毁

    Java语言拥有一套完整的垃圾回收机制. 何种对象会被java虚拟机视为垃圾.主要包括以下两种情况: (1)对象引用超过其作用范围,则这个对象将被视为垃圾 (2)将对象赋值为null 参考资料:Jav ...

  4. 一起入门python5之for循环

    昨天中午本来写了的,结果手贱了一下ctrl+x以后又去复制了别的东西.结果所有写的都没有了.蛋疼.继续写吧.今天来说for循环即条件判断>>> age = 20        #首先 ...

  5. codeforces 323A. Black-and-White Cube 构造

    输入n 1 <= n <= 100 有一个n * n * n 的立方体,由n ^ 3 个1 * 1 * 1 的单位立方体构成 要用white 和 black 2种颜色来染这n ^ 3个立方 ...

  6. 区域医疗移动医疗影像解决方案1-基于HTML5的PACS

    系统描述: 1.系统基于HTML5开发,突破了平台限制,可以在任意移动终端的浏览器上调阅原始海量医学影像图像. 2.客户端无需任何下载安装,直接通过浏览器即可使用,并处理基于DICOM标准的高保真医学 ...

  7. HDU 3452 Bonsai&lpar;网络流之最小割)

    题目地址:HDU 3452 最小割水题. 源点为根节点.再另设一汇点,汇点与叶子连边. 对叶子结点的推断是看度数是否为1. 代码例如以下: #include <iostream> #inc ...

  8. 简单学JAVA之---接口的定义与实现

    为了巩固自己学习的知识,可能会对自己以后所学的知识做一个小结,今天就来一篇接口的定义与实现. 在java中,我们可以通过继承得到另一个类中的方法,但是这个仅仅满足继承一个方法,那有办法可以多个继承不, ...

  9. android keytool 不是内部命令或外部命令在 (win7下不能用的解决方法)

    android 关于MD5指纹中 keytool在win7下不能用的解决方法 只要在cmd中执行如下命令即可:注意C:\Users\Administrator\.android\debug.keyst ...

  10. VS2012 TFS切换账号登录

    最近要做团队项目,用到的vs2012的tfs代码管理器(win7 +vs2012),切换账号的流程如下: 1.打开控制面板,进入用户账户 2.点击左侧的管理您的凭据,看到自己的TFS服务器的地址,然后 ...