【Python入门】49.异步IO之 协程

摘要:介绍什么是异步IO,什么是协程。在Python中是如何通过Generator实现协程运行的。


*写在前面:为了更好的学习python,博主记录下自己的学习路程。本学习笔记基于廖雪峰的Python教程,如有侵权,请告知删除。欢迎与博主一起学习Pythonヽ( ̄▽ ̄)ノ *


本学习笔记基于廖雪峰的Python教程。欢迎与博主一起学习Pythonヽ( ̄▽ ̄)ノ
本节内容:介绍什么是异步IO,什么是协程。在Python中是如何通过Generator实现协程运行的。

目录

异步IO
协程
Generator的send()函数
Generator实现协程

异步IO

在IO编程一节中,我们知道CPU的速度远远快于磁盘、网络等IO。在一个线程中,遇到IO操作时,CPU往往需要等待IO操作完成后才能执行下一步操作,这种情况称为同步IO

为了加快代码的运行速度,我们可以使用多进程或多线程来并发执行代码,解决一个线程被阻塞而影响其他代码运行的问题。

但是系统运行线程的数量也是有限的,而且当线程数量过多,CPU忙于切换线程而非执行代码,运行效率大大降低。

要解决这个问题,就要用到异步IO

当代码需要执行一个IO操作时,只发出IO指令,让磁盘去执行,而CPU不等待iO结果,继续执行其他代码。一段时间后,当IO返回结果时,再通知CPU进行处理。这种情况称为异步IO

在同步IO的情况下,遇到IO操作,主线程被挂起,阻塞了其他代码的运行;
在异步IO的情况下,遇到IO操作,一个线程可以处理多个多个IO请求,大大提高系统的多任务处理能力。

协程

在学习异步IO之前,我们需要了解一个重要的概念——协程

协程(Coroutine),又称微线程,纤程。

我们回顾一下进程与线程。

进程:我们每打开一个程序就是打开一个进程,比如一个浏览器,一个游戏等;
线程:在一个进程中,会包含多个线程,比如在浏览器中,我们可以看视频,听音乐等。

在同步机制下,一个线程就是执行一个子程序,或者我们称之为函数。子程序的调用只有一个入口,一次返回,且调用顺序是明确的。

而协程与上面所说的线程不一样。

协程看上去也是子程序,但执行过程中,在一个子程序可中断,然后去执行另一个子程序(不是函数调用),在适当的时候再返回来接着执行。

看个简单的示例:

def A():
    print(1)
    print(2)
    print(3)

def B():
    print('a')
    print('b')
    print('c')

现在有两个子程序A和B,如果由线程来执行,执行A打印123,执行B打印abc。如果由协程来执行,在执行A的过程中,可中断然后去执行B,然后再回来执行A,打印结果可能是这样的:

1
2
a
b
3
c

这看起来像是多线程,但实际上只有一个线程,这便是协程的特点。

这个特点使得协程有两大优势,第一,不存在切换线程的开销,子程序的切换靠程序自身的控制;第二,不需要多线程的锁机制,因为只有一个线程。这使得协程的效率比多线程高很多。

所以,用多进程+协程的方式,多核CPU的充分利用加上协程的高效率,使得系统运行获得极高的性能。

Generator的send()函数

在Python中,通过生成器generator来实现协程。

还记得生成器generator吗?

generator是一种一边循环一边计算的机制,它保存的是一种算法,可以通过next()函数来调用并返回计算结果。

def A():
    n = 0
    while True:
        n = n + 1
        print('Start')
        yield n
        print('End')
a = A()

现在a是一个generator,我们可以通过不断调用next()函数来返回计算结果n的值:

>>>next(a)
Start 
1
>>>next(a)
End
Start 
2
>>>next(a)
End
Start 
3

注意到,第一次调用next()时,代码执行到第一个yield(包含yield),下一次调用next(),会接着从上一次yield语句后面的代码开始执行。

了解了next()函数的执行过程后,我们再来看send()函数。send()与next()类似,但多了一个赋值的功能。

我们先把yield n改为x = yield n,再添加print(x)语句:

def A():
    n = 0
    while True:
        n = n + 1
        print('Start')
        x = yield n
        print(x)
        print('End')
a = A()

这个时候我们依然可以调用next(),代码不会出错:

>>>next(a)
Start 
1
>>>next(a)
None
End
Start 
2

注意到这里多输出了一个None,说明yield n实际上是一个表达式,而它的值为None

send()函数的作用就是可以给yield n赋值,像这样:

>>>a.send(None)
Start 
1 
>>>a.send(100)
100 
End 
Start 
2 

执行send()函数时,是先给yield n赋值,然后执行yield后面的语句。这里把100赋给了变量x

由于第一次调用send()函数时没有可以赋值的对象,所以必须使用send(None)send(None)的作用与next()是一样的。

Generator实现协程

我们通过Generator的send()函数来不断切换子程序,从而实现协程的运行。

来看一个生产者—消费者模型的例子 (例子源自廖雪峰官网)

传统的做法是用一个线程来生产信息,一个线程来获取信息,通过锁机制控制队列和等待,但一不小心就可能死锁。

改用协程,生产者在生产信息后,通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'

def produce(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)

执行结果:

[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK

代码解析:
函数consumer()是一个生成器,把consumer传入函数produce()执行:

首先在produce中,调用c.send(None)启动生成器consumer

produce生产信息之后,通过调用r = c.send(n),向consumer发送信息n,并且返回consumer的消费情况r

n=5时,produce停止生产,调用c.close()关闭生成器consumer

整个流程由一个线程执行,produceconsumer协作完成任务,这种方式便是协程。

子程序就是协程的一种特例。——Donald Knuth


以上就是本节的全部内容,感谢你的阅读。

下一节内容:异步IO之

有任何问题与想法,欢迎评论与吐槽。

和博主一起学习Python吧( ̄▽ ̄)~*

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

推荐阅读更多精彩内容

  • 异步IO CPU的速度远远快于磁盘、网络等IO。在一个线程中,CPU执行代码的速度极快,然而,一旦遇到IO操作,如...
    时间之友阅读 6,480评论 4 17
  • 原创文章出自公众号:「码农富哥」,如需转载请请注明出处!文章如果对你有收获,可以收藏转发,这会给我一个大大鼓励哟!...
    大富帅阅读 9,886评论 3 21
  • Coroutine in Python 引言: 本文出自David Beazley 的关于协程的PPT,现在笔者将...
    LumiaXu阅读 1,594评论 4 8
  • 一. 操作系统概念 操作系统位于底层硬件与应用软件之间的一层.工作方式: 向下管理硬件,向上提供接口.操作系统进行...
    月亮是我踢弯得阅读 5,948评论 3 28
  • 就在搞定甲方于扬那一单之后开始吧,陈可有点“飘”了却不自知。她一直把老顾当学习的榜样,但学的多是些外在的东西,看起...
    池浅笑安然阅读 231评论 0 0