这篇博客会用三种方式实现协程

1、yield

2、greenlet

3、gevent

我们先来看下第一种方式,使用yield实现协程

1、先来复习一下yield,如果一个函数中有yield,那么他就是一个生成器

def test():
    print("ok")
    yield
# 如果一个函数有yield,那么他就不是一个普通的函数,而是一个生成器,这个yield相当于普通函数的return



test()
# print(test())
#如果是一个生成器,那么这段代码不会去执行函数test的,而仅仅是生成一个生成器对象,我们通过print就可以看的出来

# 如果想执行这个生成器,只能生成器对象调用next方法来执行生成器这个函数

# next(test())
# 这样就执行了上面的生成器的函数,上面这个例子中yield后没有任何值,那么他们返回值就是默认的None,我们用下面的代码就可以获取到yield后面的值
#在这个例子中,我们可以看到a就可以接受到yield后面的值,我们打印a就可以到a的值为None
a = next(test())
print(a)

def test2():
    print(id(test2))
    yield 2
    # next方法执行这里到就会返回一个值2,然后退出函数

b = next(test2())
print(b)
# 这里yield后面的值为2,那么我们打印b就可以得到b的值为2

# 如果函数中有一个yield就可以执行一个next方法,如果有2个,就可以执行2个next方法,一次类推

# yield的作用:相当于函数中的return,next方法执行到这里,就会退出,如果在来一个next,就接着yield后面的代码继续执行



# 上面的例子,只能函数返回值,下面的例子中,我们可以给这个函数传递值


def test3():
    print("test23")
    count = yield 3
    print(count)
    yield
a = test3()
c = next(a)

# 1、通过next方法进入生成器,执行到yield 3这段代码,就执行结束
print(c)
# 这里可以打印c的值为yield后面的值,为3

c = a.send("aa")
# 这里通过send再次进入,通过send方法传递一个aa进去,这个aa就会赋值给你yield前面的变量,也就是count
print(c)

  

在来看下通过yiled实现协程,我们用yield来实现一个吃包子的例子

# 协程:在单线程下实现并发,协程是一种用户态的轻量级线程


# 好处
# 1、无需上下文切换,因为只有一个线程,所以无需要在不同的cpu之间切换
# 2、无需加锁
# 3、方便切换控制流,简化编程模型
# 4、高并发+高扩展+低成本,一个cpu并发上万个协程都是没问题的

# 资源消耗
# 进程>线程>协程


#
# 不好的地方
# 1、由于是在单线程下实现的协程,那么他就无法利用多核的优势,可以通过多进程+协程的方式实现,进程可以利用到多核的优势,开多个进程,每个进程开一个线程,在协程在开多个协程来实现
# 2、如果出现阻塞,则会阻塞整个程序
import time

def eat():
    print("老子来要吃包子了")
    while True:
        num = yield
        print("我吃的包子是{0}".format(num))



def  create(name):
    num = 1
    # e1 = eat()
    # next(e1)
    # 1、进入生成器的方式1
    # e1.send(None)
    # 2、进入生成器的方式2


    # 第一次进入生成器,不能用send传参的方法进入生成器,会报错的,我们只能用2中方式进入生成器
    # 1、next(e1)
    # 2、e1.send(None),用send传参数,传一个none
    print("{0}要来做包子了".format(name))
    while True:
        time.sleep(0.1)
        print("我做的包子是{0}".format(num))
        e1.send(num)
        num = num + 1

if __name__ == '__main__':
    e1 = eat()
    next(e1)
    create("2B",)

  

2、在看通过greenlet实现协程,遇到switch就切换

# import gevent
from greenlet import greenlet




def test1():
    print(12)
    g2.switch()
    print(34)
    g2.switch()

def test2():
    print("56")
    g1.switch()
    print("78")

if __name__ == '__main__':
    g1 = greenlet(test1)
    g2 = greenlet(test2)
    g1.switch()

  

3、在看通过gevent实现协程,我们用gevent.sleep模拟io阻塞

import gevent
import time
def test1():
    n = 1
    print("这是test1函数的第{1}句{0}".format(time.ctime(),n))
    gevent.sleep(2)
    print("这是test1函数的第{1}句{0}".format(time.ctime(),n + 1))

def test2():
    n = 1
    print("这是test2函数的第{1}句{0}".format(time.ctime(),n))
    gevent.sleep(1)
    print("这是test2函数的第{1}句{0}".format(time.ctime(),n + 1))



if __name__ == '__main__':
    gevent.joinall(
        [
            gevent.spawn(test1),
            gevent.spawn(test2)
        ]
    )

  

我们在看一个使用gevent实例协程的爬虫的例子

import gevent
from urllib.request import urlopen
import time
from gevent import monkey
monkey.patch_all()
# 这一句是一个补丁,打上这个补丁,切换就会更快一些,主要是windows上起作用

def test(url):
    print("我要准备跑网址【{0}】".format(url))
    resp = urlopen(url)
    data = resp.read()
    print("网址【{0}】的长度是【{1}】".format(url,len(data)))


test_list = ['https://www.python.org/','https://www.126.com/','https://www.baidu.com/']

if __name__ == '__main__':
    start1_time = time.time()
    for i in range(20):
        gevent.joinall(
            [
                gevent.spawn(test,test_list[0]),
                gevent.spawn(test, test_list[1]),
                gevent.spawn(test, test_list[2]),
            ]
        )
    end1_time = time.time()


    start_time = time.time()
    for i in range(20):
        for url in test_list:
            test(url)
    end_time = time.time()
    print("协程的时间时间是{0}".format(end1_time - start1_time))
    print("函数话费的时间是{0}".format(end_time - start_time))

  

posted on 2018-01-11 21:51  bainianminguo  阅读(167)  评论(0编辑  收藏  举报