Python协程

什么是协程

协程,又叫做微线程和纤程,英文名叫做coroutine。协程是用户级别的轻量级线程。

前面我们讲过线程,Python中线程调度和操作系统的进程调度类似,都属于抢占式的调度。而协程是程序级别的,根据程序员的需求自己调度。子程序,或者说函数是层级调用的,它是一个入口,一次返回,并且调用顺序明确。协程就是在子程序执行的过程中,转而执行别的子程序,然后在返回来接着执行,这个过程并不是函数调用,而是中断。

协程拥有自己的寄存器上下文和栈。协程调度时,将寄存器上下文和栈保存到其他的地方,之后切回来使,恢复之前保存的寄存器上下文和栈继续执行。

为什么要使用协程

Python中,我们已经有了线程和进程,为什么还要使用协程呢?

上面已经提到,协程是程序员写调度逻辑的,不需要CPU进行线程的切换,因此也就省下了切换上下文的开销,提升了性能。

同时协程不需要使用多线程中的锁机制,只有一个线程,因此在协程中控制共享资源时不需加锁,所以执行效率也比多线程高。

获取你也注意到,协程其实就是一个单线程,那么自然它也无法利用多核资源。因此在现实中,我们通常是使用多进程+协程的方法,这样既充分的利用多核的优势,又充分发挥协程的高效率,从而获得高性能。

如何使用协程

协程的实现由很多种,在此我们介绍基本的三种方法。

(1)使用yield实现协程

我们使用生产者/消费者的例子讲解协程的使用。

def consumer(name):
	print("consumer %s 要开始吃东西了"%name)
	while True:
		bone=yield
		print("%s正在吃东西 %s"%(name,bone))
	
def producer(obj1,obj2):
	obj1.send(None)   #和obj1.__next__()等价
	obj2.send(None)    
	n=0
	while n<5:
		n+=1
		print("producer 正在生产食品 %s"%n)
		obj1.send(n)
		obj2.send(n)
		
if __name__=='__main__':
	con1=consumer("张三")
	con2=consumer("李四")
	producer(con1,con2)    

运行结果为:

Python协程

不明白yield的运行机制的可以看看我之前的文章。

(2)使用greenlet实现协程

使用greenlet模块可以进行手动切换。

from greenlet import greenlet

def work1():
	print("1111")
	gr2.switch()  #切换执行work2
	print("2222")
	gr2.switch()   #切换回work2之前执行到的位置,接着执行
	
def work2():
	print("3333")
	gr1.switch()   #切换回work1之前执行到的位置,接着执行
	print("4444")
	
	
gr1=greenlet(work1)  #启动一个协程
gr2=greenlet(work2)
gr1.switch()  #跳转至协程gr1

运行结果为:

Python协程

(3)使用gevent实现协程

使用greenlet模块时我们需要进行人工切换,而gevent会自动识别程序内部的IO操作,当子程序遇到IO时,它会自动切换到子程序。若所有的子程序进入到IO,则阻塞。

import gevent

def work1():
	print("work1 running")
	gevent.sleep(2)  #内部函数实现IO操作
	print("switch work1")
	
def work2():
	print("work2 running")
	gevent.sleep(1)
	print("switch work2")
	
def work3():
	print("work3 running")
	gevent.sleep(1)
	print("switch work3")
	
#创建并开始执行协程
th1=gevent.spawn(work1)
th2=gevent.spawn(work2)
th3=gevent.spawn(work3)
#阻塞等待协程结束
gevent.joinall([th1,th2,th3])  

运行结果为:

Python协程

我们可以通过一个例子来感受一下同步执行和使用协程进行异步执行的性能差别。

import gevent

def task(pid):
	gevent.sleep(1)
	print("Task %s done" % pid)
	
def synchronous():    #同步
	for i in range(1,10):
		task(i)
		
def asynchronous():   #异步
	threads=[gevent.spawn(task,i) for i in range(10)]
	gevent.joinall(threads)
	
print('synchronous')
synchronous()
print('asynchronous')
asynchronous()

运行一下程序可以发现使用协程进行异步执行效率相对于同步执行是非常高的。