Python 实现协程(四)

我们本节即将学习的 Python asyncio 包,使用基于事件循环驱动的协程实现并发。这是 Python 中最大,也是最具雄心壮志的库之一。

既然 asyncio 基于事件驱动,那么让我们首先来了解下事件驱动编程,再进入正题。

一. 事件驱动

1.1 单线程、多进程以及事件驱动编程模型的比较

事件驱动编程是一种编程范式,程序的执行流程由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时,使用一种回调机制来触发相应的处理。

此外,单线程同步以及多进程也是常见的编程范式。下图对比了单线程、多线程以及事件驱动编程模型。

上图中,这个程序有 A / B / C 个任务需要完成,每个任务在执行过程中都存在 IO 阻塞,阻塞的时间使用黑色块表示。

单线程同步模型:多个任务按序执行。一旦某个任务因为 I/O 而阻塞,其他所有的任务都必须等待,直到前面的任务完成之后它们才能依次执行。即使任务之间并没有互相依赖,仍然需要等待,使得程序不必要的降低了运行速度。

多进程同步模型中:各个任务分别在独立的进程中执行。进程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行。与单线程同步程序相比,多进程的效率更高,但同时创建进程的资源消耗也比较大。

多线程操作共享资源时,还需要考虑同步互斥机制,而且 CPython 解释器无法利用计算机多核的特性。

事件驱动编程模型中:多个任务在一个单独的线程中交错执行。当遇到 I/O 操作时,注册一个回调到事件循环中,然后当 I/O 操作完成时继续执行。

事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。因此,一个任务在遇到 IO 阻塞时,可以让步出 CPU 的使用权,让其它任务继续执行,而不是一直等待。事件驱动编程模型不需要关心线程安全问题。

我们之前介绍的 IO 多路复用,使用的就是事件驱动编程模型,利用 select / poll / epoll 将 IO 事件交给系统内核监控,当某个 IO 描述符结束阻塞准备就绪时,就将其返回。

1.2 协程的引入

事件驱动编程模型有诸多好处,但在嵌套多层回调时,可读性较差,出现异常排查也很困难,非常不利于后期的维护。

于是,我们引入协程来解决上面的问题,允许我们 采用同步的方式去编写异步的代码,使代码的可读性提升,既操作简单,速度又快

协程使用单线程去切换任务,性能远高于线程切换,且不需要加锁,并发性高。

进程、线程以及协程的关系可以使用下图描述:

进程可以包含多个线程,多个线程共享进程的资源,因此线程比进程更轻量;而协程的本质是一个函数,一个线程可以包含多个协程,协程比线程更轻量。

1.3 相关概念

  • 并发:CPU 在多个任务之间不断切换,比如在一秒内 CPU 切换了 100 个进程,就可以认为 CPU 的并发是 100。
  • 并行:在多核 CPU 中,多个任务在不同的 CPU 上同时运行;并行数量和 CPU 数量是一致的。
  • 同步:必须等待前一个调用完成后,再开始新的的调用。
  • 异步:不必等待前一个操作的完成,就开始新的的调用。
  • 阻塞:调用函数的时候,当前线程被挂起。
  • 非阻塞:调用函数的时候,当前线程不会被挂起,而是立即返回结果(不管什么样的结果)。

二. asyncio 模块

Python3.4 中引入 asyncio 模块,创建协程函数时使用@asyncio.coroutine 装饰器装饰。

我们前面介绍的 yield frompython3.4 前的用法,即包含 yield from 语句的函数即可作为生成器函数,也可以称作协程函数。

Python3.4 之后,使用 @asyncio.coroutine 装饰的函数即可称作协程函数。关于 asyncio 中的基本概念总结如下:

术语 说明
coroutine 协程对象 使用 @asyncio.coroutine 装饰器装饰的函数被称作协程函数,它的调用不会立即执行,而是返回一个协程对象。协程对象需要包装成任务注入到事件循环,由事件循环调用。
task 任务 使用协程对象作为参数创建任务,任务是协程对象的进一步封装,其包含任务的各种状态
event_loop 事件循环 协程函数必须添加到事件循环中,由事件循环去运行,因为直接调用协程函数返回的是协程对象,协程函数并不会真正开始运行。事件循环控制任务运行流程,是任务的调用方。

示例 asyncio 实现协程的简单示例

import time
import asyncio
 
 
@asyncio.coroutine
def do_some_work():
    print('Coroutine Start.')
    time.sleep(3)  # 模拟IO操作
    print('Print in coroutine.')
 
 
def main():
    start = time.time()
    loop = asyncio.get_event_loop()
    coroutine = do_some_work()
    loop.run_until_complete(coroutine)
    end = time.time()
    print('运行耗时:{:.2f}'.format(end - start))  # 打印程序运行耗时
 
 
if __name__ == '__main__':
    main()

运行结果:首先使用协程装饰器 @asyncio.coroutine 创建协程函数,协程函数中使用 time.sleep(3) 模拟一个耗时的IO操作。

asyncio.get_event_loop() 用来创建事件循环;每个线程中只能有一个事件循环,get_event_loop 获取当前已经存在的事件循环,如果当前线程中没有,则新建一个事件循环。

loop.run_until_complete(coroutine) 将协程对象注入到事件循环,协程的运行由事件循环控制。事件循环的 run_until_complete 方法会阻塞运行,直到任务全部完成。

协程对象作为 run_until_complete 方法的参数,loop 会自动将协程对象包装成任务来运行。下节我们会讲到多个任务注入事件循环的情况。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容