深入了解python中的协程函数


本文摘自php中文网,作者零到壹度,侵删。

本篇文章给大家分享的内容是深入了解python中的协程函数 ,有着一定的参考价值,有需要的朋友可以参考一下

概念:

根据维基百科给出的定义,“协程 是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序”。从技术的角度来说,“协程就是你可以暂停执行的函数”。如果你把它理解成“就像生成器一样”,那么你就想对了。

协程,又称为微线程,看上去像是子程序,但是它和子程序又不太一样,它在执行的过程中,可以在中断当前的子程序后去执行别的子程序,再返回来执行之前的子程序,但是它的相关信息还是之前的。

协程不同于线程,线程是抢占式的调度,而协程是协同式的调度,协程需要自己做调度。
子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

协程的优点:

  • 协程优势是极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。用来执行协程多任务非常合适。

  • 协程没有线程的安全问题。一个进程可以同时存在多个协程,但是只有一个协程是激活的,而且协程的激活和休眠又是程序员通过编程来控制,而不是操作系统控制的。

生成器实现协程原理

示例:

1

2

3

4

5

6

7

8

9

10

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方法完全等价。

生成器实现生产者和消费者模式:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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)

1

2

3

4

5

6

7

8

9

10

11

打印:

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扩展库。

代码示意:

1

2

3

4

5

6

7

8

9

10

11

12

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 和 协程 威力。

代码示意:

1

2

3

4

5

6

7

8

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实现并发服务器

1

2

3

4

5

6

7

8

9

10

11

12

13

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和队列的生产者和消费者模式

1

2

3

4

5

6

7

8

9

10

11

12

13

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])

1

2

打印:

生产了33消费了33生产了95消费了95生产了92消费了92...


相关推荐:

python中多进程+协程的使用

python中协程

Python 协程的详细用法和例子

python 协程示例

以上就是深入了解python中的协程函数的详细内容,更多文章请关注木庄网络博客!!

相关阅读 >>

Python用什么软件写爬虫

Python3.8.0安装教程

如何检测一个变量是否存在

Python注释用什么符号

Python语言是由哪个人创造的

Python中面向对象该如何编程

总结Python中执行命令的几种方法

Python语言命名规则是什么?

Python实现超简单的视频对象提取功能

a[1:]在Python什么意思

更多相关阅读请进入《Python》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...