对比了三种情况下采集50个网页所需时间,可以看出多线程在效率上是远高于gevent的。第一次测试的时候,没有使用monkey这个补丁,socket是阻塞调用的,效率并没有提升,因为还是同步运行的,使用monkey补丁后,使socket变为协作运行,效率大大提升。
Python的运行环境允许我们在运行时修改大部分的对象,包括模块,类甚至函数。 这是个一般说来令人惊奇的坏主意,因为它创造了“隐式的副作用”,如果出现问题 它很多时候是极难调试的。虽然如此,在极端情况下当一个库需要修改Python本身 的基础行为的时候,猴子补丁就派上用场了。在这种情况下,gevent能够 修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协作式运行。
例如,Redis的python绑定一般使用常规的tcp socket来与redis-server实例通信。 通过简单地调用gevent.monkey.patch_all(),可以使得redis的绑定协作式的调度 请求,与gevent栈的其它部分一起工作。
这让我们可以将一般不能与gevent共同工作的库结合起来,而不用写哪怕一行代码。 虽然猴子补丁仍然是邪恶的(evil),但在这种情况下它是“有用的邪恶(useful evil)”。
执行的结果分别是
同步执行:4.50s
gevent异步:0.47s
threading多线程:0.58s
更新asyncio: 1.1s
gevent并不是同时执行,还是按顺序执行,并未打乱输出结果,多线程不按顺序执行,打乱了输出结果。网络状况好(IO操作延时低)的情况下,gevent能稍微提高点效率,IO操作很费时的情况gevent效率将大大提高,效率最高的还是多线程。
gevent:当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。
gevent - 廖雪峰的官方网站
# /usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright (c) 2017 - walker <cail1844@gmail.com>
import gevent
import requests
import re
import timeit
import codecs
from threading import Thread
from gevent import monkey
monkey.patch_socket()
def get_title(url,title_list=[]):
try:
r = requests.get(url,timeout=5)
r.encoding = 'utf8'
html = r.text
title = re.search(r'<title>(.*?)</title>',html).group(1)
except TimeoutError:
title = ''
if title:
title_list.append(title)
return title
def get_url():
baseurl = 'http://www.baidu.com/s?cl=3&tn=baidutop10&fr=top1000&wd=%s'
f = codecs.open('muci.txt','r','utf8')
url_list = []
for key in f.readlines():
url_list.append(baseurl % key.strip())
return url_list
url_list = get_url()
def run1():
title_list = []
for url in url_list:
get_title(url,title_list)
# title_list.append(title)
print('Sync result length:',len(title_list),title_list)
return title_list
def run2():
title_list = []
threads = [gevent.spawn(get_title,url,title_list) for url in url_list]
gevent.joinall(threads)
# title_list = [thread.value for thread in threads]
print('gevent result length:',len(title_list),title_list)
return title_list
def run3():
title_list = []
th = []
for url in url_list:
t = Thread(target=get_title,args=(url,title_list))
th.append(t)
t.start()
for t in th:
t.join()
print('threading result length:',len(title_list),title_list)
return title_list
if __name__ == '__main__':
t1 = timeit.timeit('run1()',setup="from __main__ import run1",number=1)
print('sync time:',t1)
t2 = timeit.timeit('run2()',setup="from __main__ import run2",number=1)
print('gevent time:',t2)
t3 = timeit.timeit('run3()',setup="from __main__ import run3",number=1)
print('thread time:',t3)
放到django下查看页面生成的时间
同步:19065ms
gevent:1555ms
threading:1166ms
更新
增加python3.5下的asyncio测试,时间比gevent和threading长一点,1.1s
async def get_title2():
loop = asyncio.get_event_loop()
title_list = []
io = []
for url in url_list:
# get_title(url, title_list)
io.append(loop.run_in_executor(None,get_title,url,title_list))
for i in io:
await i
print(threading.current_thread(), 'Sync result length:', len(title_list), title_list)
return title_list
def run4():
loop = asyncio.get_event_loop()
loop.run_until_complete(get_title2())
if __name__ == '__main__':
global url_list
url_list = get_url()
t4 = timeit.timeit('run4()', setup="from __main__ import run4", number=1)
print('thread time:', t4)