:我正在扩大答案,以包括一个更完善的示例。我在这篇文章中发现了很多关于线程与异步I / O的敌意和错误信息。因此,我还添加了更多论点来驳斥某些无效的主张。我希望这将帮助人们为正确的工作选择正确的工具。
这是3天前提出的问题的答案。
Python urllib2.open运行缓慢,需要一种更好的方法来读取多个URL-堆栈溢出 Python urllib2.urlopen()运行缓慢,需要一种更好的方法来读取多个URL
import time
import threading
import Queue
# utility - spawn a thread to execute target for each args
def run_parallel_in_threads(target, args_list):
result = Queue.Queue()
# wrapper to collect return value in a Queue
def task_wrapper(*args):
result.put(target(*args))
threads = [threading.Thread(target=task_wrapper, args=args) for args in args_list]
for t in threads:
t.start()
for t in threads:
t.join()
return result
def dummy_task(n):
for i in xrange(n):
time.sleep(0.1)
return n
# below is the application code
urls = [
('http://www.google.com/',),
('http://www.lycos.com/',),
('http://www.bing.com/',),
('http://www.altavista.com/',),
('http://achewood.com/',),
]
def fetch(url):
return urllib2.urlopen(url).read()
run_parallel_in_threads(fetch, urls)
如您所见,特定于应用程序的代码只有3行,如果您比较积极,可以将其折叠为1行。我认为没有人能证明他们的主张是复杂且不可维持的。
不幸的是,这里发布的大多数其他线程代码都有一些缺陷。他们中的许多人都进行主动轮询以等待代码完成。join()
是同步代码的更好方法。我认为到目前为止,此代码已对所有线程示例进行了改进。
如果您所有的URL都指向同一服务器,那么WoLpH关于使用保持活动连接的建议可能会非常有用。
亚伦·加拉格尔(Aaron Gallagher)是twisted
框架的狂热者,他对任何建议使用线程的人都怀有敌意。不幸的是,他的许多说法都是错误的信息。例如,他说“ -1表示线程建议。这是IO绑定的;线程在这里无用。” 这与证据相反,因为我和Nick T都已经证明了使用线程的速度提高。实际上,使用Python的线程可以最大程度地提高I / O绑定的应用程序的收益(而cpu绑定的应用程序则没有收益)。Aaron对线程的误导性批评表明,他对并行编程感到困惑。
我很清楚与使用线程,python,异步I / O等进行并行编程有关的问题。每个工具都有其优缺点。对于每种情况,都有一个适当的工具。我不反对扭曲(尽管我自己还没有部署)。但是我不相信我们可以断定在所有情况下线程都是错误的,而扭曲是良好的。
例如,如果OP的要求是并行获取10,000个网站,则异步I / O是可取的。线程是不适当的(除非使用无堆栈Python)。
亚伦对线程的反对大多是概括。他未能意识到这是一项琐碎的并行化任务。每个任务都是独立的,并且不共享资源。因此,他的大部分攻击都不适用。
鉴于我的代码没有外部依赖关系,因此我将其称为用于正确工作的正确工具。
我认为大多数人都会同意,此任务的性能在很大程度上取决于网络代码和外部服务器,而平台代码的性能应在这些方面可以忽略不计。但是Aaron的基准测试显示,与线程代码相比,速度提高了50%。我认为有必要对这种明显的速度提高做出反应。
在尼克的代码中,有一个明显的缺陷导致效率低下。但是您如何解释我的代码比233ms的速度提高呢?我认为即使是扭曲的粉丝也不会做出结论,将其归因于扭曲的效率。毕竟,系统代码之外还有大量变量,例如远程服务器的性能,网络,缓存以及urllib2和扭曲的Web客户端之间的差异实现等。
为了确保Python的线程不会导致大量的低效率,我做了一个快速基准测试,先生成5个线程,然后生成500个线程。我很舒服地说生成5个线程的开销可以忽略不计,并且不能解释233ms的速度差异。
In [274]: %time run_parallel_in_threads(dummy_task, [(0,)]*5)
cpu times: user 0.00 s, sys: 0.00 s, total: 0.00 s
Wall time: 0.00 s
Out[275]: <Queue.Queue instance at 0x038B2878>
In [276]: %time run_parallel_in_threads(dummy_task, [(0,)]*500)
cpu times: user 0.16 s, sys: 0.00 s, total: 0.16 s
Wall time: 0.16 s
In [278]: %time run_parallel_in_threads(dummy_task, [(10,)]*500)
cpu times: user 1.13 s, sys: 0.00 s, total: 1.13 s
Wall time: 1.13 s <<<<<<<< This means 0.13s of overhead
在我的并行读取中进行的进一步测试显示,在17次运行中响应时间存在巨大差异。(不幸的是,我没有费力去验证Aaron的代码)。
0.75 s
0.38 s
0.59 s
0.38 s
0.62 s
1.50 s
0.49 s
0.36 s
0.95 s
0.43 s
0.61 s
0.81 s
0.46 s
1.21 s
2.87 s
1.04 s
1.72 s
我的测试不支持亚伦的结论,即线程始终比异步I / O慢很多。给定涉及的变量数量,我不得不说这不是衡量异步I / O与线程之间系统性能差异的有效测试。