美文网首页编程随笔
python 协程和异步I/O的实践

python 协程和异步I/O的实践

作者: jj_jump | 来源:发表于2020-03-09 22:25 被阅读0次
    Image

    python 协程和异步I/O的实践

    协程的概念

    协程(coroutine)通常又称之为微线程或纤程,它是相互协作的一组子程序(函数)。所谓相互协作指的是在执行函数A时,可以随时中断去执行函数B,然后又中断继续执行函数A。注意,这一过程并不是函数调用(因为没有调用语句),整个过程看似像多线程,然而协程只有一个线程执行。协程通过yield关键字和 send()操作来转移执行权,协程之间不是调用者与被调用者的关系。

    协程的优势在于以下两点:

    • 执行效率极高,因为子程序(函数)切换不是线程切换,由程序自身控制,没有切换线程的开销。

    • 不需要多线程的锁机制,因为只有一个线程,也不存在竞争资源的问题,当然也就不需要对资源加锁保护,因此执行效率高很多。

    • 总结:协程适合处理的是I/O密集型任务,处理CPU密集型任务并不是它的长处,如果要提升CPU的利用率可以考虑“多进程+协程”的模式。

    • 示例代码:

    def consumer():
        """
        消费者
        :return:
        """
        count = 0
        while True:
            n = yield count
            if not n:
                return
            print('Consumer %s...' % n)
            count += n
    
    
    def produce(c):
        """
        生产者
        :param c:
        :return:
        """
        # 激活协程
        c.send(None)
        n = 0
        while n < 5:
            n = n + 1
            print('Produce %s...' % n)
            r = c.send(n)
            print('Consumer return: %s' % r)
        c.close()
    
    
    if __name__ == '__main__':
        c = consumer()
        produce(c)
    
    
    

    注意 : consumer函数是一个generator,把一个consumer传入produce后:

    首先调用c.send(None)启动生成器;
    然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
    consumer通过yield拿到消息,处理,又通过yield把结果传回;
    produce拿到consumer处理的结果,继续生产下一条消息;
    produce决定不生产了,通过c.close()关闭consumer,整个过程结束。
    整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

    历史回顾

    1. Python 2.2:第一次提出了生成器(最初称之为迭代器)的概念(PEP 255)。
    2. Python 2.5:引入了将对象发送回暂停了的生成器这一特性即生成器的send()方法(PEP 342)。
    3. Python 3.3:添加了yield from特性,允许从迭代器中返回任何值(注意生成器本身也是迭代器),这样我们就可以串联生成器并且重构出更好的生成器。
    4. Python 3.4:引入asyncio.coroutine装饰器用来标记作为协程的函数,协程函数和asyncio及其事件循环一起使用,来实现异步I/O操作。
    5. Python 3.5:引入了async和await,可以使用async def来定义一个协程函数,这个函数中不能包含任何形式的yield语句,但是可以使用return或await从协程中返回值。

    异步I/O - 非阻塞式I/O操作。

    • 用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。
    • 示例代码:
    
    import asyncio
    
    
    @asyncio.coroutine
    def countdown(name, num):
        while num > 0:
            print(f'Countdown[{name}]: {num}')
            yield from asyncio.sleep(1)
            num -= 1
    
    
    def main():
        loop = asyncio.get_event_loop()
        tasks = [
            countdown("A", 10), countdown("B", 5),
        ]
        loop.run_until_complete(asyncio.wait(tasks))
        loop.close()
    
    
    if __name__ == '__main__':
        main()
    
    • 为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读

    • 请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:

      • 把@asyncio.coroutine替换为async;
      • 把yield from替换为await。
    • 示例代码:

    
    import asyncio
    
    
    async def countdown(name, num):
        while num > 0:
            print(f'Countdown[{name}]: {num}')
            await asyncio.sleep(1)
            num -= 1
    
    
    def main():
        loop = asyncio.get_event_loop()
        tasks = [
            countdown("A", 10), countdown("B", 5),
        ]
        loop.run_until_complete(asyncio.wait(tasks))
        loop.close()
    
    
    if __name__ == '__main__':
        main()
    
    
    

    参考链接

    相关文章

      网友评论

        本文标题:python 协程和异步I/O的实践

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