美文网首页
【Python入门】49.异步IO之 协程

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

作者: 三贝_ | 来源:发表于2018-09-24 20:46 被阅读13次

    摘要:介绍什么是异步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吧( ̄▽ ̄)~*

    相关文章

      网友评论

          本文标题:【Python入门】49.异步IO之 协程

          本文链接:https://www.haomeiwen.com/subject/xwjxoftx.html