【操作系统知识】python实现协程

7. python实现协程

(1) yield + send实现

通过“生产者-消费者”模型来看下协程的应用,生产者产生消息后,直接通过yield跳转到消费者开始执行,带消费者执行完毕后,切换回生产者继续生产。

# 利用生成器实现协程
# 来源:https://juejin.im/post/5d888151f265da03dd3db0f5
def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print(f"[CONSUMER] Consuming {n} ...")
        r = '200 OK'

def producer(c):
    # 启动生成器
    c.send(None)
    n = 0
    while n < 5:
        n += 1
        print(f'[PRODUCER] Producing {n} ...')
        r = c.send(n)
        print(f'[PRODUCER] Consumer return: {r}')
    c.close()


if __name__ == "__main__":
    c = consumer()
    producer(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
'''

send(msg)next()的区别在于send可以传递参数给yield表达式,这时传递的参数会作为yield表达式的值,而yield的参数是返回给调用者的值。换句话说,就是send可以强行修改上一个yield表达式的值。比如函数中有一个yield赋值a = yield 5,第一次迭代到这里会返回5,a还没有赋值。第二次迭代时,使用send(10),那么就是强行修改yield 5表达式的值为10,本来是5的,结果a = 10send(msg)next()都有返回值,它们的返回值是当前迭代遇到yield时,yield后面表达式的值,其实就是当前迭代中yield后面的参数。第一次调用send时必须是send(None),否则会报错,之所以为None是因为这时候还没有一个yield表达式可以用来赋值。

(2) asyncio + yield from实现

asyncio是Python3.4版本引入的标准库,直接内置了对IO的支持。asyncio的异步操作,需要在coroutine中通过yield from 完成。

import asyncio

@asyncio.coroutine
def test(i):
    print("test_1", i)
    r = yield from asyncio.sleep(1)
    print('test_2', i)

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    tasks = [test(i) for i in range(3)]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()

# 结果
'''
test_1 2
test_1 1
test_1 0
test_2 2
test_2 1
test_2 0
'''

@asyncio.coroutine 把一个generator标记为coroutine类型,然后就把这个coroutine扔到EventLoop中执行。test()会首先打印出test_1,然后yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。把asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。

(3) asyncio + asnyc/await实现

为了简化并耕海的标识异步IO,从Python3.5开始,引入了新的语法asyncawait,可以让coroutine的代码更简洁易读。实际上,就是async替换@asyncio.coroutine,await替换yield from

import asyncio

async def test(i):
    print('test_1', i)
    await asyncio.sleep(1)
    print('test_2', i)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    tasks = [test(i) for i in range(3)]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()

# 结果
'''
test_1 2
test_1 1
test_1 0
test_2 2
test_2 1
test_2 0
'''

(4) greenlet 实现

Gevent是一个基于Greenlet实现的网络库,通过greenlet实现协程。基本思想是一个greenlet就认为是一个协程,当一个greenlet遇到IO操作的时候,比如访问网络,就会自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO操作。

import gevent

def test(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # gevent.sleep(1)  # 把3个greenlet依次运行改为交替运行

if __name__ == '__main__':
    g1 = gevent.spawn(test, 3)
    g2 = gevent.spawn(test, 3)
    g3 = gevent.spawn(test, 3)

    g1.join()
    g2.join()
    g3.join()

# 结果
'''
<Greenlet at 0x1ee82d8c268: test(3)> 0
<Greenlet at 0x1ee82d8c268: test(3)> 1
<Greenlet at 0x1ee82d8c268: test(3)> 2
<Greenlet at 0x1ee82d8c378: test(3)> 0
<Greenlet at 0x1ee82d8c378: test(3)> 1
<Greenlet at 0x1ee82d8c378: test(3)> 2
<Greenlet at 0x1ee82d8c488: test(3)> 0
<Greenlet at 0x1ee82d8c488: test(3)> 1
<Greenlet at 0x1ee82d8c488: test(3)> 2
'''

当然在实际的代码里,我们不会用gevent.sleep()去切换协程,而是在执行到IO操作时gevent会自动完成,所以gevent需要将Python自带的一些标准库的运行方式由阻塞式调用变为协作式运行。这一过程在启动时通过monkey patch完成:

from gevent import monkey; monkey.patch_all()
from urllib import request
import gevent

def test(url):
    print(f"Get: {url}")
    response = request.urlopen(url)
    content = response.read().decode("utf-8")
    print(f"{len(content)} bytes received from {url}.")

if __name__ == "__main__":
    gevent.joinall([
        gevent.spawn(test, 'http://httpbin.org/ip'),
        gevent.spawn(test, 'http://httpbin.org/uuid'),
        gevent.spawn(test, 'http://httpbin.org/user-agent')
    ])

# 结果,3个网络操作是并发操作的,而且结束的顺序不同,但只有一个线程
'''
Get: http://httpbin.org/ip
Get: http://httpbin.org/uuid
Get: http://httpbin.org/user-agent
46 bytes received from http://httpbin.org/ip.
53 bytes received from http://httpbin.org/uuid.
40 bytes received from http://httpbin.org/user-agent.
'''
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值