摘要:创建第一个协程推荐使用语法来声明协程,来编写异步应用程序。协程两个紧密相关的概念是协程函数通过定义的函数协程对象调用协程函数返回的对象。它是一个低层级的可等待对象,表示一个异步操作的最终结果。
我们讲以Python 3.7 上的asyncio为例讲解如何使用Python的异步IO。
创建第一个协程Python 3.7 推荐使用 async/await 语法来声明协程,来编写异步应用程序。我们来创建第一个协程函数:首先打印一行“你好”,等待1秒钟后再打印“猿人学”。
sayhi()函数通过 async 声明为协程函数,较之前的修饰器声明更简洁明了。
在实践过程中,什么功能的函数要用async声明为协程函数呢?就是那些能发挥异步IO性能的函数,比如读写文件、读写网络、读写数据库,这些都是浪费时间的IO操作,把它们协程化、异步化从而提高程序的整体效率(速度)。
sayhi()函数是通过 asyncio.run()来运行的,而不是直接调用这个函数(协程)。因为,直接调用并不会把它加入调度日程,而只是简单的返回一个协程对象:
那么,如何真正运行一个协程呢?asyncio 提供了三种机制:
(1)asyncio.run() 函数,这是异步程序的主入口,相当于C语言中的main函数。
(2)用await等待协程,比如上例中的 await asyncio.sleep(1) 。再看下面的例子,我们定义了协程 say_delay() ,在main()协程中调用两次,第一次延迟1秒后打印“你好”,第二次延迟2秒后打印“猿人学”。这样我们通过 await 运行了两个协程。
从起止时间可以看出,两个协程是顺序执行的,总共耗时1+2=3秒。
(3)通过 asyncio.create_task() 函数并发运行作为 asyncio 任务(Task) 的多个协程。下面,我们用create_task()来修改上面的main()协程,从而让两个say_delay()协程并发运行:
从运行结果的起止时间可以看出,两个协程是并发执行的了,总耗时等于最大耗时2秒。
asyncio.create_task() 是一个很有用的函数,在爬虫中它可以帮助我们实现大量并发去下载网页。在Python 3.6中与它对应的是 ensure_future()。
可等待对象(awaitables)可等待对象,就是可以在 await 表达式中使用的对象,前面我们已经接触了两种可等待对象的类型:协程和任务,还有一个是低层级的Future。
asyncio模块的许多API都需要传入可等待对象,比如 run(), create_task() 等等。
(1)协程协程是可等待对象,可以在其它协程中被等待。协程两个紧密相关的概念是:
协程函数:通过 async def 定义的函数;
协程对象:调用协程函数返回的对象。
运行上面这段程序,结果为:
co is now is 1548512708.2026224 now is 1548512708.202648
可以看到,直接运行协程函数 whattime()得到的co是一个协程对象,因为协程对象是可等待的,所以通过 await 得到真正的当前时间。now2是直接await 协程函数,也得到了当前时间的返回值。
(2)任务前面我们讲到,任务是用来调度协程的,以便并发执行协程。当一个协程通过 asyncio.create_task() 被打包为一个 任务,该协程将自动加入程序调度日程准备立即运行。
create_task()的基本使用前面例子已经讲过。它返回的task通过await来等待其运行完。如果,我们不等待,会发生什么?“准备立即运行”又该如何理解呢?先看看下面这个例子:
运行这段代码的情况是这样的:
首先,1秒钟后打印一行,这是第13,14行代码运行的结果:
calling:0, now is 09:15:15
接着,停顿1秒后,连续打印4行:
calling:1, now is 09:15:16 calling:2, now is 09:15:16 calling:3, now is 09:15:16 calling:4, now is 09:15:16
从这个结果看,asyncio.create_task()产生的4个任务,我们并没有await,它们也执行了。关键在于第18行的 await,如果把这一行去掉或是sleep的时间小于1秒(比whattime()里面的sleep时间少即可),就会只看到第一行的输出结果而看不到后面四行的输出。这是因为,main()不sleep或sleep少于1秒钟,main()就在whattime()还未来得及打印结果(因为,它要sleep 1秒)就退出了,从而整个程序也退出了,就没有whattime()的输出结果。
再来理解一下“准备立即执行”这个说法。它的意思就是,create_task()只是打包了协程并加入调度队列还未执行,并准备立即执行,什么时候执行呢?在“主协程”(调用create_task()的协程)挂起的时候,这里的“挂起”有两个方式:
一是,通过 await task 来执行这个任务;
另一个是,主协程通过 await sleep 挂起,事件循环就去执行task了。
我们知道,asyncio是通过事件循环实现异步的。在主协程 main()里面,没有遇到 await 时,事件就是执行main()函数,遇到 await 时,事件循环就去执行别的协程,即create_task()生成的whattime()的4个任务,这些任务一开始就是 await sleep 1秒。这时候,主协程和4个任务协程都挂起了,CPU空闲,事件循环等待协程的消息。
如果main()协程只sleep了0.1秒,它就先醒了,给事件循环发消息,事件循环就来继续执行main()协程,而main()后面已经没有代码,就退出该协程,退出它也就意味着整个程序退出,4个任务就没机会打印结果;
如果main()协程sleep时间多余1秒,那么4个任务先唤醒,就会得到全部的打印结果;
如果main()的18行sleep等于1秒时,和4个任务的sleep时间相同,也会得到全部打印结果。这是为什么呢?
我猜想是这样的:4个任务生成在前,第18行的sleep在后,事件循环的消息响应可能有个先进先出的顺序。后面深入asyncio的代码专门研究一下这个猜想正确与否。
(3)Future它是一个低层级的可等待对象,表示一个异步操作的最终结果。目前,我们写应用程序还用不到它,暂不学习。
asyncio异步IO协程总结协程就是我们异步操作的片段。通常,写程序都会把全部功能分成很多不同功能的函数,目的是为了结构清晰;进一步,把那些涉及耗费时间的IO操作(读写文件、数据库、网络)的函数通过 async def 异步化,就是异步编程。
那些异步函数(协程函数)都是通过消息机制被事件循环管理调度着,整个程序的执行是单线程的,但是某个协程A进行IO时,事件循环就去执行其它协程非IO的代码。当事件循环收到协程A结束IO的消息时,就又回来执行协程A,这样事件循环不断在协程之间转换,充分利用了IO的闲置时间,从而并发的进行多个IO操作,这就是异步IO。
写异步IO程序时记住一个准则:需要IO的地方异步。其它地方即使用了协程函数也是没用的。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/43851.html
摘要:并发的方式有多种,多线程,多进程,异步等。多线程和多进程之间的场景切换和通讯代价很高,不适合密集型的场景关于多线程和多进程的特点已经超出本文讨论的范畴,有兴趣的同学可以自行搜索深入理解。 编程中,我们经常会遇到并发这个概念,目的是让软件能充分利用硬件资源,提高性能。并发的方式有多种,多线程,多进程,异步IO等。多线程和多进程更多应用于CPU密集型的场景,比如科学计算的时间都耗费在CPU...
摘要:具有以下基本同步原语子进程提供了通过创建和管理子进程的。虽然队列不是线程安全的,但它们被设计为专门用于代码。表示异步操作的最终结果。 Python的asyncio是使用 async/await 语法编写并发代码的标准库。通过上一节的讲解,我们了解了它不断变化的发展历史。到了Python最新稳定版 3.7 这个版本,asyncio又做了比较大的调整,把这个库的API分为了 高层级API和...
摘要:理解迭代对象迭代器生成器后端掘金本文源自作者的一篇博文,原文是,俺写的这篇文章是按照自己的理解做的参考翻译。比较的是两个对象的内容是后端掘金黑魔法之协程异步后端掘金本文为作者原创,转载请先与作者联系。 完全理解关键字with与上下文管理器 - 掘金如果你有阅读源码的习惯,可能会看到一些优秀的代码经常出现带有 with 关键字的语句,它通常用在什么场景呢?今天就来说说 with 和 上下...
摘要:以下这些项目,你拿来学习学习练练手。当你每个步骤都能做到很优秀的时候,你应该考虑如何组合这四个步骤,使你的爬虫达到效率最高,也就是所谓的爬虫策略问题,爬虫策略学习不是一朝一夕的事情,建议多看看一些比较优秀的爬虫的设计方案,比如说。 (一)如何学习Python 学习Python大致可以分为以下几个阶段: 1.刚上手的时候肯定是先过一遍Python最基本的知识,比如说:变量、数据结构、语法...
阅读 1371·2023-04-26 03:04
阅读 2211·2019-08-30 15:44
阅读 3709·2019-08-30 14:15
阅读 3453·2019-08-27 10:56
阅读 2653·2019-08-26 13:53
阅读 2593·2019-08-26 13:26
阅读 3042·2019-08-26 12:11
阅读 3576·2019-08-23 18:21