Python协程之协程在手,说走就走

协程在手,说走就走

什么是协程

先介绍–生产者-消费者模式

解释如下

生产者消费者模式并不是GOF提出的23种设计模式之一,23种设计模式都是建立在面向对象的基础之上的,但其实面向过程的编程中也有很多高效的编程模式,生产者消费者模式便是其中之一,它是我们编程过程中最常用的一种设计模式。
在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。

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

def consumer():
    # 消费者
    r = ''
    while True:
        n = yield r # 将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() # 类似于iter(range(10)),即
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

生产者每生成一个数,消费者接收到并返回接受状态,细看代码,也就用到了个yieldsend,但这正是python实现协程的核心。

首先分析一下代码的执行

生产者首先c.send(None),启动消费者生成器,一旦启动生成器,消费者那边的yieldr发送到消费者这边,是个空的字符串。

一旦消费者yield,程序就跑到生产者这边了,消费者就暂停执行,也就是说n = yield r只执行了yield r,n都没有赋值的。

然后消费者这边开始执行while循环,n自增,然后就执行r=send(n),这边的send是带参数的,这个参数会发送到消费者那边,给上次的n进行赋值,也就是说生产者通过send将消费者从暂停状态激活了。

那么消费者的n赋值为1后,就继续执行消费者的程序了,打印并且将r进行赋值,然后又到了yield r,这就将重新赋值的r又发送给了生产者的r

。。。

就这样一直迭代着,生产者给生产n,消费者发给生产者r

看懂了吗?这到底说了个啥?为什么要这样干?都把人绕晕了…

消费者反而是个生成器,真有意思。

这个模型的核心在于利用yield实现了函数间的中断,也就是协程的核心。

把上面两个函数看成是两个普通函数,也就是说一个两个函数能够随时中断去执行另外一个函数,这就很神奇了。

定义协程

python提供了async去定义一个协程函数,await进行中断挂起去执行其他协程。

import asyncio
import time

async def main():
    print('hello')
    await asyncio.sleep(1)
    print('world')

start = time.time()
asyncio.run(main())
print(f'cost time: {time.time() - start}')

输出

hello
[暂停了一秒]
world
cost time: 1.0051383972167969

定义一个协程就是这么简单,可是这也看不出协程的优点啊,一毫秒的时间也没有节省,那是因为这只有一个协程,也不用切换到其他协程。

那么使用多个协程看一下效果

import asyncio
import time

async def main():
    print('hello')
    await asyncio.sleep(1)
    print('world')

start = time.time()
asyncio.run(asyncio.wait([main(), main()]))
print(f'cost time: {time.time() - start}')

这里使用了两个协程,看一下效果

hello
hello
world
world
cost time: 1.0056376457214355

时间几乎一样,如果不用协程,执行函数肯定最少要2s,但现在只要1s,极大缩短了运行时间。

那么协程和多线程哪个更快呢?有人做了实验进行了对比爬虫协程比线程爬取速度更快? - 北风之神0509 - 博客园 (cnblogs.com)

应用

协程应用比较多的还是IO操作,包括网络IO,文件操作IO,将所有IO操作都进行await,这样可以极大缩短运行时间。

写代码的时候建议多进程和协程结合使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值