这篇博客会用三种方式实现协程
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))
浙公网安备 33010602011771号