本篇文章给大家分享的内容是深入了解python中的协程函数 ,有着一定的参考价值,有需要的朋友可以参考一下
概念:根据维基百科给出的定义,“协程 是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序”。从技术的角度来说,“协程就是你可以暂停执行的函数”。如果你把它理解成“就像生成器一样”,那么你就想对了。
协程,又称为微线程,看上去像是子程序,但是它和子程序又不太一样,它在执行的过程中,可以在中断当前的子程序后去执行别的子程序,再返回来执行之前的子程序,但是它的相关信息还是之前的。
协程不同于线程,线程是抢占式的调度,而协程是协同式的调度,协程需要自己做调度。
子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
协程的优点:
协程优势是极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。用来执行协程多任务非常合适。
协程没有线程的安全问题。一个进程可以同时存在多个协程,但是只有一个协程是激活的,而且协程的激活和休眠又是程序员通过编程来控制,而不是操作系统控制的。
生成器实现协程原理示例:
def func(n):
index=0
if index<=n:
c=yield 1
print("task------{}".format(c))
index+=1f=func(3)
n=next(f)
print(n)try:
n=f.send(5)#程序就直接结束了
print("n是{}".format(n))except stopiteration as e: pass
输出打印:1task------5
解释说明:
很明显func是一个生成器,send方法有一个参数,该参数指定的是上一次被挂起的yield语句的返回值。
send需要做异常处理。
总的来说,send方法和next方法唯一的区别是在执行send方法会首先把上一次挂起的yield语句的返回值通过参数设定,从而实现与生成器方法的交互。但是需要注意,在一个生成器对象没有执行next方法之前,由于没有yield语句被挂起,所以执行send方法会报错。
send方法的参数为none时,它与next方法完全等价。
生成器实现生产者和消费者模式:
def cunsumer():
while true:
n=yield 3
if not n: return
print('cunsumer{}'.format(n))def product(c):
c.send(none)
n=0
while n<5:
n=n+1
r=c.send(n)
print("product{}".format(r))
c.close()
c=cunsumer()
product(c)
打印:
cunsumer1
product3
cunsumer2
product3
cunsumer3
product3
cunsumer4
product3
cunsumer5
product3
解释说明:
在生产者里先执行了 c.send(none),目的是先让消费者挂起,再用send传值,第一次传1,消费者那里打印1,生产者打印r是消费者yield后面的值。
greenlet 的引入虽然cpython(标准python)能够通过生成器来实现协程,但使用起来还并不是很方便。
与此同时,python的一个衍生版 stackless python实现了原生的协程,它更利于使用。
于是,大家开始将 stackless 中关于协程的代码 单独拿出来做成了cpython的扩展包。
这就是 greenlet 的由来,因此 greenlet 是底层实现了原生协程的 c扩展库。
代码示意:
from greenlet import greenletimport randomimport timedef producer():
while true:
item = random.randint(0,10)
print("生产了{}".format(item))
c.switch(item)#切换到消费者,并将item传入消费者
time.sleep(1)def consumer():
print('我先执行') #p.switch()
while true:
item = p.switch()#切换到生产者,并且等待生产者传入item
print('消费了{}'.format(item))
c = greenlet(consumer)#将一个普通函数变成一个协程p = greenlet(producer)
c.switch()#让消费者先进入暂停状态(只有恢复了才能接收数据)
greenlet 的价值:
高性能的原生协程
语义更加明确的显式切换
直接将函数包装成协程,保持原有代码风格
gevent协程虽然,我们有了 基于 epoll 的回调式编程模式,但是却难以使用。
即使我们可以通过配合 生成器协程 进行复杂的封装,以简化编程难度。
但是仍然有一个大的问题: 封装难度大,现有代码几乎完全要重写
gevent,通过封装了 libev(基于epoll) 和 greenlet 两个库。
帮我们做好封装,允许我们以类似于线程的方式使用协程。
以至于我们几乎不用重写原来的代码就能充分利用 epoll 和 协程 威力。
代码示意:
from gevent import monkey;monkey.patch_all()#会把python标准库当中一些阻塞操作变成非阻塞import geventdef test1():
print("11")
gevent.sleep(4)#模拟爬虫请求阻塞
print("33")def test2():
print("22")
gevent.sleep(4)
print("44")
gevent.joinall([gevent.spawn(test1),gevent.spawn(test2)])#joinall 阻塞当前协程,执行给定的greenlet#spawn 启动协程,参数就是函数的名字
gevent 的价值:
遇到阻塞就切换到另一个协程继续执行 !
使用基于 epoll 的 libev 来避开阻塞。
使用基于 gevent 的 高效协程 来切换执行。
只在遇到阻塞的时候切换,没有轮需的开销,也没有线程的开销。
gevent实现并发服务器
from gevent import monkey;monkey.patch_all() #建议放在首行,会把python标准库当中一些阻塞操作变成非阻塞import geventimport socket
server=socket.socket()
server.bind(('',6666))
server.listen(5)
print("开始监听")def readable(con,addr):
print("客户端{}接入".format(addr)) while true:
data=con.recv(1024) if data:
print(data) else:
con.close() breakwhile true:
con,addr=server.accept()
gevent.spawn(readable,con,addr)#将readable函数变为协程,并且把con和addr传入其中。
gevent 协程通信gevent也有自己的队列。使用方式和进线程基本一样。
基于gevent和队列的生产者和消费者模式
from gevent import monkey;monkey.patch_all()import geventfrom gevent.queue import queueimport randomdef producter(queue):
while true:
item=random.randint(0,99)
print('生产了{}'.format(item))
queue.put(item)
gevent.sleep(1)def comuser(queue):
while true:
item=queue.get()
print('消费了{}'.format(item))
queue=queue()
p=gevent.spawn(producter,queue)
c=gevent.spawn(comuser,queue)
gevent.joinall([p,c])
打印:
生产了33消费了33生产了95消费了95生产了92消费了92...
相关推荐:
python中多进程+协程的使用
python中协程
python 协程的详细用法和例子
python 协程示例
以上就是深入了解python中的协程函数的详细内容。