欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

深入了解python中的协程函数

程序员文章站 2022-05-09 22:42:27
...

本篇文章给大家分享的内容是深入了解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中的协程函数的详细内容,更多请关注其它相关文章!