gevent完成多任务
一、原理
gevent实现多任务并不是依靠多进程或是线程,执行的时候只有一个线程,在遇到堵塞的时候去寻找可以执行的代码。本质上是一种协程。
二、代码实现
import gevent def f1(n): for i in range(n): print(gevent.getcurrent(), i) gevent.sleep(0.5) def f2(n): for i in range(n): print(gevent.getcurrent(), i) gevent.sleep(0.5) def f3(n): for i in range(n): print(gevent.getcurrent(), i) gevent.sleep(0.5) # 创建gevent对象,spawn()函数中的第二个参数是前面需要执行的函数中需要传入的参数 # 此时仅仅是创建对象并没有执行 g1 = gevent.spawn(f1, 5) g2 = gevent.spawn(f1, 5) g3 = gevent.spawn(f1, 5) # 调用join()函数的时候才开始执行 g1.join() g2.join() g3.join()
解读:
程序从上往下执行,在执行到g1.join()的时候,会去执行对应的函数,这个函数在执行的过程中会出现堵塞现象。在这个时候程序并不会一直在那里等待,而是回去继续寻找其他的gevent创建的对象,继续执行代码。在这个程序里面,会继续执行g2.join(),依此类推,后面代码的执行情况和这个一样。(这点就像是异步I/O)
注意:
- gevent.spawn()---------->用于创建gevent对象,并没有执行函数
- g1.join()---------->此时才开始执行对应的函数
- 在gevent()中要想有sleep()造成的堵塞,必须使用gevent.sleep()。(前提是没有打补丁)
三、gevent打补丁
# 打补丁,导入这个模块,并执行指定的函数,那么遇到需要耗时的操作都会将其替换成gevent()中的耗时操作 from gevent import monkey monkey.patch_all()
打完补丁之后可以使用time.sleep()
def f1(n): for i in range(n): print(gevent.getcurrent(), i) time.sleep(0.5)
如上面代码所示,将完整代码中的gevent.sleep()全部替换为time.sleep()效果不会发生改变
四、更简单的输出方式
观察上面的程序,我们发现在调用gevent创建的对象的时候,代码过于冗长。加入创建了一百个gevent对象,那岂不是要写一百个join()函数。
为了解决这个问题,gevent给我们提供了一个joinall()函数。
语法如下:
gevent.joinall([
gevent.spawn(functionname1),
gevent.spawn(functionname2),
gevent.spawn(functionname3)
])
对上面咱们已经写好的代码进行如下修改,修改完成之后代码执行效果不变。
gevent.joinall([ gevent.spawn(f1, 5), gevent.spawn(f2, 5), gevent.spawn(f3, 5) ])
五、gevent实现图片下载器
import gevent import urllib.request from gevent import monkey monkey.patch_all() def img_download(img_name, img_url): req = urllib.request.urlopen(img_url) # 获取的是响应状态 content = req.read() with open(img_name, "wb") as f: f.write(content) def main(): gevent.joinall([ gevent.spawn(img_download, "1.jpg", "http://n.sinaimg.cn/photo/transform/700/w1000h500/20211002/3c9c-4fecc919c8af637a9f6cc7c82b4cf3bc.jpg"), gevent.spawn(img_download, "2.jpg" , "https://n.sinaimg.cn/photo/400/w200h200/20210416/a6a4-knvsnuf5950596.jpg") ]) if __name__ == "__main__": main()