※ python协程
协程也称之为微线程,协程的作用是在执行函数A的情况下,可以随时中断去执行函数B,然后终端继续执行函数A,但是这个过程并没有函数调用,协程只有一个线程在执行。
所以协程由于是程序主动控制切换的,所以没有线程切换的开销(存在两个必要的开销:线程的创建和上下文的切换,切换前,上个任务的状态会被保存,切换回来,再加载这个任务的状态),所以执行效率很高,使用于IO密集型,如果是CPU密集型,推荐多进程+协程。
python对于协程的支持是通过生成器实现的,协程是遵循某些规则的生成器。
生成器的yield表达式:
__next__
启动或者恢复generator的执行,相当于send(None)
send()
作用是发送值给yield表达式
生产者和消费者模型:
传统的生产者-消费者模型是一个线程写消息,一个线程读取消息,通过锁机制控制队列和等待,但是一不小心可能就会产生死锁。
现在改成使用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率就很高了:
首先来看一下yield到底是怎么工作的:
def test():
print('start')
n = 1
while True:
r = yield n
print('ok', r)
n += 1
# 创建生成器对象
gen = test()
# 启动生成器
res = gen.__next__()
# res = gen.send(None) 其实是跟上面一行一样的作用
print('res', res)
>>> start
>>> res 1
# 发送具体的值给yield表达式
send_res = gen.send(100)
print('send res', send_res)
>>> ok 100
>>> send res 2
上面可以看到使用生成器的最开始要调用__next__
或者send(None)
方法,然后代码执行到yield n
的地方会直接暂停,直到下一次被send(100)
方法给唤醒了。
def consumer(): # 消费者
r = 'start'
while True:
n = yield r
if not n:
print{'n is empty')
continue
print('consumer consume', n)
r = '200 ok' # 运行到这里相当于r重新赋值
def producer(c): # 生产者
start_value = c.send(None) # 这里启动了生成器
print(start_value)
n = 0
while n < 3:
n += 1
print('producer produce', n)
r = c.send(n) # 一旦生成了东西,就切换到消费者执行
print('consumer return', r)
c.close()
c = consumer() # 创建了一个生成器
producer(c) # 将生成器当做是参数传入
>>> start
>>> producer produce 1
>>> consumer consume 1
>>> consumer return 200 ok
>>> ...
yield from的用法:
当一个生成器函数需要处理另一个生成器生成的值,通常的用法是两个for循环嵌套:
for a in iterables:
for b in a:
yield b
但是yield from完全替代了内层的for循环:
for a in iterables:
yield from b
asyncio
这个库直接内置了对异步io的支持,这个库的模型就是消息循环,从该模块获取一个EventLoop的引用,然后把需要执行的协程放到里面,就实现了异步IO
import threading
import asyncio
@asyncio.coroutine # 把这个生成器标记成协程
def hello():
print('Hello1 world! (%s)' % threading.currentThread())
yield from asyncio.sleep(4) # 这里调用了另一个生成器
# 由于上面生成器也是一个协程,所以不会等待,而是直接中断并执行下一个消息循环
# 把上面的sleep看成是1秒的IO操作,此期间,线程不会等待,而是找loop里面其他可以执行的协程
print('Hello1 again! (%s)' % threading.currentThread())
@asyncio.coroutine
def hello():
print('Hello2 world! (%s)' % threading.currentThread())
yield from asyncio.sleep(2)
print('Hello2 again! (%s)' % threading.currentThread())
loop = asyncio.get_event_loop()
tasks = [hello1(), hello2()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
输出:
Hello1 world! (<_MainThread(MainThread, started 140368765929216)>)
Hello2 world! (<_MainThread(MainThread, started 140368765929216)>)
暂停2秒
Hello2 again! (<_MainThread(MainThread, started 140368765929216)>)
再暂停2秒
Hello1 again! (<_MainThread(MainThread, started 140368765929216)>)
其实上面就模拟了,两个协程切换执行的情况,hello1先执行,遇到了相当于4秒的IO操作,然后切换到协程hello2执行,遇到了2秒的IO操作,这个时候没有可以切换的协程了,所以先执行完协程hello2,然后此时hello1还有2秒的IO操作。
上面根据线程ID可以看出,2个函数通过由一个线程通过coroutine并发完成
async/await
上面的asyncio其实就是通过该库提供的@asyncio.coroutine
装饰器将一个生成器标记为协程类型,然后在协程内部调用yield from
调用另一个协程实现异步操作。
使用最新的async和await只需要做一个简单的替换:
@asyncio.coroutine
-->async
yield from
-->await
使得回调的写法更加直观
所以改写上面的例子:
async def hello1()
print('hello1')
r = await asyncio.sleep(1)
print('hello1 again')
greenlet
前面说到的使用原生的generator是一个semicoroutine半协同程式,但是greenlet是一个真正的协程,生成器实现时yield value的时候,只能将value返回给调用者,但是在greenlet中,可以切换到指定的协程。
from greenlet import greenlet
def test1():
print 12
gr2.switch()
print 34
def test2():
print 56
gr1.switch()
print 78
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
>>> 12 56 34
创建一个greenlet时,会初始化一个空的栈。switch这个栈的时候,会运行构造greenlet时传入的函数,如果过程中切换到了另一个,那么该协程会被挂起,等到切换回来。
对于greenlet,最常用的写法就是x = gr.switch(y)
,就是切换到gr协程,并且传入参数y,然后切换回来的时候,将值赋值给x
协程的生命周期:只有当协程对应的函数执行完毕后,协程才会挂掉。如果试图切换到一个已经挂掉的协程上时,实际上会切换到他的parent greenlet。
gevent!!!
gevent是Python的第三方库,通过greenlet实现了协程,核心思想是:当一个greenlet遇到IO操作的时候,就自动切换到其他的greenlet,等到IO操作完成再切换回来。gevent主要目的是自动切换协程,这样就能保证总有greenlet在运行。
如上面所说,因为切换greenlet是在IO操作的时候,自动完成的,所以gevent需要修改python的一些自带的标准库,这个过程是通过monkey patch完成的:
from gevent import monkey; monkey.patch_socket()
import gevent
def f(n):
for i in range(n):
print gevent.getcurrent(), i
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
在这种情况下,其实会发现三个greenlet是依次运行的,这是因为并没有遇到IO操作,所以如果我们加上一个gevent.sleep()
运行的话,那么他们就会交替运行。
gevent只能在Linux下运行,在Windows下不保证正常安装和运行。