这篇博客会用三种方式实现协程
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))