python携程

介绍

协程(coroutine),又称为微线程,纤程。协程的作用:在执行A函数的时候,可以随时中断,去执行B函数,然后中断继续执行A函数(可以自动切换),单着一过程并不是函数调用(没有调用语句),过程很像多线程,然而协程只有一个线程在执行
子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。
协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:

def A():
    print(1)
    print(2)


def B():
    print(x)
    print(y)
#假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:
1
x
2
y

但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。

优势

看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行,那和多线程比,协程有何优势?
最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

yield实现协程

先来再复习生成器:
yield是用于生成器。生成器通俗的认为,在一个函数中,使用了yield来代替return的位置的函数,就是生成器。它不同于函数的使用方法是:函数使用return来进行返回值,每调用一次,返回一个新加工好的数据返回给你;yield不同,它会在调用生成器的时候,把数据生成object,然后当需要用的时候,要用next()方法来取,同时不可逆。
如果一个函数中有return返回值,而且在return下面还有代码:那么return下面的代码将不会被执行,而yield却不同,它返回值后还可以继续执行下面的代码。

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高(yield实现):

import time

def cosumer(name):
    """
    这是一个生成器,调用时候才执行
    :param name: 消费者名字
    :return:包子
    """
    print('消费者准备吃包子')
    while True:
        new_baozi = yield
        print('%s吃第%d个包子'%(name,new_baozi))
        time.sleep(1)
def producer(name):
    r = con.__next__()
    r = con2.__next__()
    count = 1
    while True:
        print('%s正在生产第%s个包子和%d个包子'%(name,count,count+1))
        # 调用生成器并且发送数据
        con.send(count)
        con2.send(count+1)
        count += 2
if __name__ == '__main__':
    con = cosumer('李云龙')
    con2 = cosumer('翠花')
    p = producer('大厨')

#结果:
消费者准备吃包子
消费者准备吃包子
大厨正在生产第1个包子和2个包子
李云龙吃第1个包子
翠花吃第2个包子
大厨正在生产第3个包子和4个包子
李云龙吃第3个包子
翠花吃第4个包子
大厨正在生产第5个包子和6个包子
李云龙吃第5个包子
翠花吃第6个包子
大厨正在生产第7个包子和8个包子
李云龙吃第7个包子
翠花吃第8个包子
大厨正在生产第9个包子和10个包子
李云龙吃第9个包子
  1. 首先调用c.next()启动生成器;

  2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;

  3. consumer通过yield拿到消息,处理,又通过yield把结果传回;

  4. producer拿到consumer处理的结果,继续生产下一条消息;

模块greenlet实现

greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之
间随意切换,而不需把这个函数先声明为generator

一些方法:
g = greenlet(run=None, parent=None):实例化一个greenlet对象
g.parent:每一个协程都有一个父协程,当前协程结束后会回到父协程中执行,该属性默认是创建该协程的协程
g.run: 该属性是协程实际运行的代码. run方法结束了,那么该协程也就结束了
g.switch(*args, **kwargs): 切换到g协程
g.throw(): 切换到g协程,接着抛出一个异常

from greenlet import greenlet

def work1():
    print(12)
    # 3.切换到test2()中
    gr2.switch()
    print(45)
    gr2.switch()
def work2():
    print(98)
    gr1.switch()
    print(50)

#1.将要执行的函数封装到greenlet对象
gr1 = greenlet(work1)
gr2 = greenlet(work2)
#2.想要执行哪个就可以使用 对象.swith()
gr1.switch()

结果:
在这里插入图片描述
greenlet模块

  • gr1=greenlet(目标函数)
  • gr1.switch() 切换执行

模块gevent实现

gevent模块安装遇到问题:pycharm获取源问题,使用清华大学源下载的。
Gevent是一种基于协程的Python网络库,它用到Greenlet提供的,封装了libevent事件循环的高层同步API。它让开发者在不改变编程习惯的同时,用同步的方式写异步I/O的代码。
当我们的程序受限于网络的 IO 阻塞时,gevent 才能真正发挥实力,它提供了方法,可以隐形的交出上下文执行权,这样我们可以在不改变程序结构的情况下来实现协程。

import requests
import time
import gevent
def f(url):
    print("get",url)
    resp = requests.get(url)
    data = resp.text
    print("%d byets recevied from %s"%(len(data),url))

#1.普通模式
s=time.time()
f( "http://www.langlang2017.com/img/banner1.png")
f( "http://www.langlang2017.com/img/banner2.png")
f( "http://www.langlang2017.com/img/banner3.png")
f( "http://www.langlang2017.com/img/banner4.png")
e = time.time()
print("普通模式时间",e-s)
#2.使用gevent模块
start = time.time()
gevent.joinall([gevent.spawn(f,"http://www.langlang2017.com/img/banner1.png"),
                gevent.spawn(f, "http://www.langlang2017.com/img/banner2.png"),
                gevent.spawn(f, "http://www.langlang2017.com/img/banner3.png"),
                gevent.spawn(f, "http://www.langlang2017.com/img/banner4.png")])#创建一个普通的greenlet对象并切换

print("gevent时间",time.time()-start)
"""
如果下载量小的话,普通模式(串行)的下载可能会比gevent模块下载快
如果下载的数据量大的话,gevent性能才能显示出来
"""


爱,就是你和某个人一起经历的一切。
原文地址:https://www.cnblogs.com/afly-8/p/13561135.html