异步IO是个好东西,在网络读写场景中可以大大提高程序的并发能力,比如爬虫、web服务等。这样的好东西自然也要在Python中可以使用。不过,在漫长的Python2时代,官方并没有推出一个自己的异步IO库,到了Python 3.4 才推出。我们先来看看异步IO在Python中的发展历史。

Python 异步IO的历史

Python 2的异步IO库

Python 2 时代官方并没有异步IO的支持,但是有几个第三方库通过事件或事件循环(Event Loop)实现了异步IO,它们是:twisted: 是事件驱动的网络库

gevent: greenlet + libevent(后来是libev或libuv)。通过协程(greenlet)和事件循环库(libev,libuv)实现的gevent使用很广泛。

tornado: 支持异步IO的web框架。自己实现了IOLOOP。

Python 3 官方的异步IO

Python 3.4 加入了asyncio 库,使得Python有了支持异步IO的官方库。这个库,底层是事件循环(EventLoop),上层是协程和任务。asyncio自从3.4 版本加入到最新的 3.7版一直在改进中。Python 3.4 刚开始的asyncio的协程还是基于生成器的,通过 yield from 语法实现,可以通过装饰器 @asyncio.coroutine(已过时)装饰一个函数来定义一个协程。比如:

Python 3.5 引入了两个新的关键字 await 和 async 用来替换 @asyncio.coroutine 和 yield from ,从语言本身来支持异步IO。从而使得异步编程更加简洁,并和普通的生成器区别开来。注意:对基于生成器的协程的支持已弃用,并计划在 Python 3.10 中移除。所以,写异步IO程序时只需使用 async 和 await 即可。Python 3.7 又进行了优化,把API分组为高层级API和低层级API。我们先看看下面的代码,发现与上面的有什么不同?除了用 async 替换 @asyncio.coroutine 和用 await 替换 yield from 外,最大的变化就是关于eventloop的代码不见了,只有一个 async.run()。这就是 3.7 的改进,把eventloop相关的API归入到低层级API,新引进run()作为高层级API让写应用程序的开发者调用,而不用再关心eventloop。除非你要写异步库(比如MySQL异步库)才会和eventloop打交道。

理解asyncio

理解asyncio并不能,关键是要动起手来,接下来我们以下面代码为例动手实践一番,通过实践来理解它。

这段代码很简单,我们定义了两个协程函数(在def前面加async),其中 hi() 我们把它叫做功能函数,通过一个 aysncio.sleep() 来模拟一个耗时的异步IO操作(比如下载网页), main() 叫做入口函数。其实就是在main() 里面调用 hi() 函数,通过不断改变 main() 的行为来理解异步IO(协程函数的调用)的运行过程。

1. 协程函数如何运行?

首先,我们要明确一个道理,hi() 是一个协程函数,直接调用它返回的是一个协程对象,并没有真正运行它。把main函数改成如下,我们来仔细看看协程函数 hi() 的运行。

下面是运行结果:

代码第19行,我们像运行普通函数一样运行 hi() ,得到的a只是一个协程对象,见结果第二行:

a is: 这个协程对象 a 虽然生成了,但是还没有运行,它需要一个时机。也就是asyncio的事件循环正在运行main,还没有空去运行它。

代码第21行,通过 await 告诉 event_loop(事件循环) ,main协程停在这里,你去运行其它协程吧。这时候 event_loop 去执行a协程,也就是去执行 hi() 函数里面的代码。等 hi() 运行完,event_loop 再回到main协程继续从21行开始执行,把 hi() 的返回值赋值给b,这时候 b 的值是1。

event_loop 在整个异步IO过程中扮演一个管家的角色,在不同的协程之间切换运行代码,切换是通过事件来进行的,通过 await 离开当前协程,await 的协程完成后又回到之前的协程对应的地方继续执行。

2. 协程函数如何并发?

异步IO的好处就是并发,但如何实现呢?我们先来看一个不是并发的例子:

这次,我们把main修改成一个for循环执行4次 hi() ,看看它运行的结果:

整个过程从21:48:30 到 21:48:40 结束,用了10秒。而hi()的执行时间分别是1秒,2秒,3秒,4秒总共10秒。也就是4个hi() 虽然是异步的但是顺序执行的,没有并发。

接下来,就到了并发的实现了,通过 asyncio.creat_task() 即可:

通过 create_task() 我们在for循环里面生成了4个task(也是协程对象),但是这4个协程任务并没有被执行,它们需要等待一个时机:当前协程(main)遇到 await。

第二个for循环开始逐一 await 协程,此时 event_loop 就可以空出手来去执行那4个协程,过程大致如下:先执行hi(1, 1) ,打印“enter hi(), 1 @21:58:35”,遇到await asyncio.sleep(1),当前协程挂起;

接着执行 hi(2, 2),执行打印命令,遇到await asyncio.sleep(2) ,当前协程挂起;

接着执行 hi(3, 3),执行打印命令,遇到await asyncio.sleep(3) ,当前协程挂起;

接着执行 hi(4, 4),执行打印命令,遇到await asyncio.sleep(4) ,当前协程挂起;

以上4步只是协程的切换和打印语句,执行非常快,我们可以任务它们是同时执行起来的。

1秒后,hi(1,1)的sleep结束它会发出事件告诉 event_loop 我await结束了,过来执行我,event_loop 此时空闲就来执行它,继续执行sleep后面的打印语句;

2秒后,hi(2,2)的sleep结束它会发出事件告诉 event_loop 我await结束了,过来执行我,event_loop 此时空闲就来执行它,继续执行sleep后面的打印语句;

3秒后,hi(3,3)的sleep结束它会发出事件告诉 event_loop 我await结束了,过来执行我,event_loop 此时空闲就来执行它,继续执行sleep后面的打印语句;

4秒后,hi(4,4)的sleep结束它会发出事件告诉 event_loop 我await结束了,过来执行我,event_loop 此时空闲就来执行它,继续执行sleep后面的打印语句;

4秒后,生成的4个协程任务就都执行完毕。总耗时4秒,也就是我们的4个任务并发完成了。

所以,上面的代码运行的结果如下:

根据上面讲述的执行流程,可以看到结果对应起来了。4个任务都是在35秒时开始执行,以后每个1秒完成一个。main函数从35执行到39介绍,共耗时4秒。

3. 错误的运行

上面的并发很完美,但有时候你可能会犯错。比如下面的main(), 你可能只是并发 hi() 函数,但不需要它的返回结果,于是有了下面的 main():

先猜猜会有什么样的结果!!

你猜对了吗?下面是运行结果:main()的for循环只是生成了4个task协程,然后就退出了。event_loop 收到main退出的事件就空出来去执行了那4个协程,进去了但都碰到了sleep。然后event_loop就空闲了。这时候run() 就收到了main() 执行完毕的事件,run() 就执行完了,最后执行print,整个程序就退出了。从main退出到整个程序退出就是一瞬间的事情,那4个协程还在傻傻的睡着,不,是在睡梦中死去了。

在main()中加一个sleep会出现什么结果:

在main()退出前,我们要先sleep 2秒,再来猜猜它的运行结果是什么?

如果你对上面没有sleep的过程搞清楚了,不难猜到正确的结果:

注意:main() 的退出和 hi(2, 2) 的退出顺序。简单讲,main() 先sleep 2秒,hi(2, 2) 后sleep两秒,所以main先退出。

理解了sleep(2) 的执行过程,那么你就可以知道 sleep(4) 和 sleep(5) 的结果了。如果没有自信的话,就自己改一下时间,运行看看结果。

4. 如何判断是否要把函数定义为协程函数?

定义一个协程函数很简单,在def前面加async即可。那么如何判断一个函数该不该定义为协程函数呢?

记住这一个原则:如果该函数是要进行IO操作(读写网络、读写文件、读写数据库等),就把它定义为协程函数,否则就是普通函数。

pythonasyncio在哪个版本好_理解Python asyncio的简洁方式相关推荐

  1. pythonasyncio在哪个版本好_什么情况下需要使用 Python 的 asyncio 模块?

    不请自来. 先说什么是Asyncio Asyncio和其他Pythongig程序同样是单线程的,只有一个主线程,但是可以进行多个不同的task,这个task是一个特殊的future对象,被event ...

  2. c++ 协程_理解Python协程(Coroutine)

    由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...

  3. python3 协程 写法_理解Python的协程(Coroutine)

    由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...

  4. python在线搭建教程_理解python web开发,轻松搭建web app!

    大家好,今天分享给大家的是理解python web开发,轻松搭建web app,希望大家学有所获! 因为 python代码的优雅美观且易于维护这一特点,越来越多的人选择使用 Python做web开发. ...

  5. python中元组_理解python中的元组

    理解 python 中的元组 引言 在 Python 中元组是这样的: 元组是是这样一种数据结构:不变的或者不可改变的(简单来说不能重新赋值) .元素的有序序列.因为元组是 不变的,所以他的数值是不能 ...

  6. 什么是python语言的动态类型机制_理解Python的Dynamic typing

    Python的Dynamic typing有些类似于C语言的指针,在C中,一个变量可以指向任何地址空间,在Python中,一个变量也可以指向任何type的数据对象.变量的指向可以在程序运行过程中变化, ...

  7. python 延时_理解Python多线程5:加锁解决问题,但又带来麻烦!

    此系列,已经推送的如下,还没看到的读者,可以走一波: 理解Python多线程4:代码稍作改动,bug就来了 理解Python多线程3:多线程抢夺同一个变量 理解Python多线程2:线程轮询得到CPU ...

  8. python更新到哪个版本了_将Python自带版本(2.6.6)升级到2.7.9

    将Python自带版本(2.6.6)升级到2.7.9 查看当前python版本:# pythonPython 2.6.6 (r266:84292, Jan 22 2014, 09:42:36) [GC ...

  9. python用渐变色画圆_利用python控制Autocad:pyautocad方式

    发现pyautocad模块:可以用python控制autocad的包.今天把文档中的重点内容摘录出来,以后绘图.计算大工程量.或者识别施工图的时候时候也许可以用到. 一.连接cad pyautocad ...

最新文章

  1. [android] 从gallery获取图片
  2. 斯坦福大学报告称中国AI论文引用率首超美国!但李国杰院士也发文灵魂拷问!...
  3. R语言tidyr包separate()函数实战详解:一列裂变为多列
  4. Linux下MySql出现#1036 – Table ‘ ‘ is read only 错误解决方法
  5. 【转】推荐两款富文本编辑器:NicEdit和Kindeditor
  6. mysql 分支 XtraDB Percona MariaDB 简介
  7. 关于Hadoop多用户管理支持客户端远程操作的理论总结
  8. PAT甲级1125 Chain the Ropes:[C++题解]贪心、优先队列、合并果子
  9. 算法学习——决策单调性优化DP
  10. 空谈Saas都扯淡,让你看看真正的云计算
  11. java编程_Java编程和C语言的比较
  12. tablepc是什么平板电脑_tablepc平板电脑怎么截图
  13. TCP Socket
  14. cpu内存和线程和pool多进程池 Python
  15. 通过duet软件实现ipad作为mac的副屏并修改分辨率
  16. CGAL license说明
  17. 轩辕剑--资料集(三)
  18. 如何将电子签名透明化处理
  19. EmguCV学习(一)
  20. cad立面索引符号 规范_cad剖面符号和索引符号该怎么画?

热门文章

  1. QT中Widget去除系统提供工具以及系统默认边框
  2. 计算机科学和机器学习中的代数学、拓扑学、微积分以及最优化理论
  3. Tomcat 处理 HTTP 请求源码分析(下)【转】
  4. Spring Remoting: Burlap--转
  5. easy ui example
  6. 深入redis内部之redis启动过程之二
  7. sql server监控
  8. Android中Uri的使用
  9. 预训练模型transformers综合总结(一)
  10. 微信小程序外卖增长402%,茶饮下单最活跃