社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

学会 Asyncio 编写异步 Python

编程派 • 6 年前 • 457 次点击  

原文:http://djangostars.com/blog/asynchronous-programming-in-python-asyncio/

译文:开源中国

如果你已经决定要理解 Python 的异步部分,欢迎来到我们的"Asyncio How-to "。

注:哪怕连异步范式的存在都不知道的情况下,你也可以成功地使用 Python。但是,如果你对底层运行模式感兴趣的话,asyncio 绝对值得查看。

异步是怎么一回事?

在传统的顺序编程中, 所有发送给解释器的指令会一条条被执行。此类代码的输出容易显现和预测。 但是…

譬如说你有一个脚本向 3 个不同服务器请求数据。 有时,谁知什么原因,发送给其中一个服务器的请求可能意外地执行了很长时间。想象一下从第二个服务器获取数据用了 10 秒钟。在你等待的时候,整个脚本实际上什么也没干。如果你可以写一个脚本可以不去等待第二个请求而是仅仅跳过它,然后开始执行第三个请求,然后回到第二个请求,执行之前离开的位置会怎么样呢。就是这样。你通过切换任务最小化了空转时间。尽管如此,当你需要一个几乎没有 I/O 的简单脚本时,你不想用异步代码。

还有一件重要的事情要提,所有代码在一个线程中运行。所以如果你想让程序的一部分在后台执行同时干一些其他事情,那是不可能的。

准备开始

这是 asyncio 主概念最基本的定义:

协程 — 消费数据的生成器,但是不生成数据。Python 2.5 介绍了一种新的语法让发送数据到生成器成为可能。我推荐查阅 David Beazley "A Curious Course on Coroutines and Concurrency" 关于协程的详细介绍。

任务 — 协程调度器。如果你观察下面的代码,你会发现它只是让 eventloop 尽快调用它的step ,同时 _step 只是调用协程的下一步。

  1. class Task(futures.Future):  

  2.     def __init__(self, coro, loop=None):

  3.         super().__init__(loop=loop)

  4.         ...

  5.         self._loop.call_soon(self._step)

  6.     def _step(self):

  7.             ...

  8.         try:

  9.             ...

  10.             result = next(self._coro)

  11.         except StopIteration as exc:

  12.             self.set_result(exc.value)

  13.         except BaseException as exc:

  14.             self.set_exception(exc)

  15.             raise

  16.         else:

  17.             ...

  18.             self._loop.call_soon(self._step)

事件循环 — 把它想成 asyncio 的中心执行器。

现在我们看一下所有这些如何融为一体。正如我之前提到的,异步代码在一个线程中运行。

从上图可知:

1.消息循环是在线程中执行

2.从队列中取得任务

3.每个任务在协程中执行下一步动作

4.如果在一个协程中调用另一个协程(await ),会触发上下文切换,挂起当前协程,并保存现场环境(变量,状态),然后载入被调用协程

5.如果协程的执行到阻塞部分(阻塞 I/O,Sleep),当前协程会挂起,并将控制权返回到线程的消息循环中,然后消息循环继续从队列中执行下一个任务...以此类推

6.队列中的所有任务执行完毕后,消息循环返回第一个任务

异步和同步的代码对比

现在我们实际验证异步模式的切实有效,我会比较两段 python 脚本,这两个脚本除了_sleep _方法外,其余部分完全相同。在第一个脚本里,我会用标准的 time.sleep 方法,在第二个脚本里使用 asyncio.sleep 的异步方法。

这里使用 Sleep 是因为它是一个用来展示异步方法如何操作 I/O 的最简单办法。

使用同步 sleep 方法的代码:

  1. import asyncio  

  2. import time  

  3. from datetime import datetime

  4. async def custom_sleep():  

  5.     print('SLEEP', datetime.now())

  6.     time.sleep(1)

  7. async def factorial(name, number):  

  8.     f = 1

  9.     for i in range(2 , number 1):

  10.         print('Task {}: Compute factorial({})'.format(name, i))

  11.         await custom_sleep()

  12.         f *= i

  13.     print('Task {}: factorial({}) is {}n'.format(name, number, f))

  14. start = time.time()  

  15. loop = asyncio.get_event_loop()

  16. tasks = [  

  17.     asyncio.ensure_future(factorial("A", 3)),

  18.     asyncio.ensure_future(factorial("B", 4)),

  19. ]

  20. loop.run_until_complete(asyncio.wait(tasks))  

  21. loop.close()

  22. end = time.time()  

  23. print("Total time: {}".format(end - start))

脚本输出:

  1. Task A: Compute factorial(2)  

  2. SLEEP 2017-04-06 13:39:56.207479  

  3. Task A: Compute factorial(3)  

  4. SLEEP 2017-04-06 13:39:57.210128   

  5. Task A: factorial(3) is 6

  6. Task B: Compute factorial(2)  

  7. SLEEP 2017-04-06 13:39:58.210778  

  8. Task B: Compute factorial(3)  

  9. SLEEP 2017-04-06 13:39:59.212510  

  10. Task B: Compute factorial(4)  

  11. SLEEP 2017-04-06 13:40:00.217308  

  12. Task B: factorial(4)  is 24

  13. Total time: 5.016386032104492

使用异步 Sleep  的代码:

  1. import asyncio  

  2. import time  

  3. from datetime import datetime

  4. async def custom_sleep():  

  5.     print('SLEEP {}n'.format(datetime.now()))

  6.     await asyncio.sleep(1)

  7. async def factorial(name, number):  

  8.     f = 1

  9.     for i in range(2, number 1):

  10.         print('Task {}: Compute factorial({})'.format(name, i))

  11.         await custom_sleep()

  12.         f *= i

  13.     print('Task {}: factorial({}) is {}n'.format(name, number, f))

  14. start = time.time()  

  15. loop = asyncio.get_event_loop()

  16. tasks = [  

  17.     asyncio.ensure_future(factorial("A", 3)),

  18.     asyncio.ensure_future(factorial("B", 4)),

  19. ]

  20. loop.run_until_complete(asyncio.wait(tasks))  

  21. loop.close()

  22. end = time.time()  

  23. print("Total time: {}".format(end - start))

脚本输出:

  1. Task A: Compute factorial(2)  

  2. SLEEP 2017-04-06 13:44:40.648665

  3. Task B: Compute factorial(2)  

  4. SLEEP 2017-04-06 13:44: 40.648859

  5. Task A: Compute factorial(3)  

  6. SLEEP 2017-04-06 13:44:41.649564

  7. Task B: Compute factorial(3)  

  8. SLEEP 2017-04-06 13:44:41.649943

  9. Task A: factorial(3) is 6

  10. Task B: Compute factorial(4)  

  11. SLEEP 2017-04-06 13:44:42.651755

  12. Task B: factorial(4) is 24

  13. Total time: 3.008226156234741

从输出可以看到,异步模式的代码执行速度快了大概两秒。当使用异步模式的时候(每次调用  await asyncio.sleep(1) ),进程控制权会返回到主程序的消息循环里,并开始运行队列的其他任务(任务A或者任务B)。

当使用标准的 sleep 方法时,当前线程会挂起等待。什么也不会做。实际上,标准的 sleep 过程中,当前线程也会返回一个 python 的解释器,可以操作现有的其他线程,但这是另一个话题了。

推荐使用异步模式编程的几个理由

很多公司的产品都广泛的使用了异步模式,如 Facebook 旗下著名的 React Native 和 RocksDB 。像 Twitter 每天可以承载 50 亿的用户访问,靠的也是异步模式编程。所以说,通过代码重构,或者改变模式方法,就能让系统工作的更快,为什么不去试一下呢?


题图:pexels,CC0 授权。

点击阅读原文,查看更多 Python 教程和资源。


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/V2kujWP566
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/4187
 
457 次点击