Python中的并行性无法正常工作
我在用 Python 2.7 开发一个应用程序,运行在 Google App Engine 上。我的应用通过 Ajax 调用从一个 API 获取数据,单个请求大约需要 200 毫秒。但是,当我打开两个浏览器并几乎同时发出两个请求时,耗时却超过了两倍。我尝试把所有操作放在线程里,但没有效果……(这种情况发生在应用上线后,而不仅仅是在开发服务器上)。
所以我写了一个简单的测试,想看看这是不是 Python 本身的问题(可能是因为忙等待),下面是代码和结果:
def work():
t = datetime.now()
print threading.currentThread(), t
i = 0
while i < 100000000:
i+=1
t2 = datetime.now()
print threading.currentThread(), t2, t2-t
if __name__ == '__main__':
print "single threaded:"
t1 = threading.Thread(target=work)
t1.start()
t1.join()
print "multi threaded:"
t1 = threading.Thread(target=work)
t1.start()
t2 = threading.Thread(target=work)
t2.start()
t1.join()
t2.join()
在 Mac OS X 上,使用的是四核八线程的 i7 处理器,Python 2.7 的结果是:
single threaded:
<Thread(Thread-1, started 4315942912)> 2011-12-06 15:38:07.763146
<Thread(Thread-1, started 4315942912)> 2011-12-06 15:38:13.091614 0:00:05.328468
multi threaded:
<Thread(Thread-2, started 4315942912)> 2011-12-06 15:38:13.091952
<Thread(Thread-3, started 4323282944)> 2011-12-06 15:38:13.102250
<Thread(Thread-3, started 4323282944)> 2011-12-06 15:38:29.221050 0:00:16.118800
<Thread(Thread-2, started 4315942912)> 2011-12-06 15:38:29.237512 0:00:16.145560
这个结果真让人震惊!如果单个线程要花 5 秒钟来完成这个操作……我原以为同时启动两个线程会花费相同的时间来完成两个任务,但实际上却几乎是三倍的时间。这让线程的想法变得毫无意义,因为顺序执行反而会更快!
我到底错过了什么呢……
3 个回答
我会先看看时间都花在哪里了。比如说,假设服务器每200毫秒只能处理一个请求。那么你就没办法了,因为服务器最多只能每200毫秒给你一个回复,这就是它的极限。
CPython解释器不允许多个线程同时运行。你可以了解一下GIL(全局解释器锁)http://wiki.python.org/moin/GlobalInterpreterLock。
所以在CPython中,有些任务不能高效地同时进行。
如果你想在GAE(谷歌应用引擎)中并行处理事情,那就要通过不同的请求来同时启动它们。
另外,你可能还想看看Python的并行处理相关的维基页面 http://wiki.python.org/moin/ParallelProcessing。
David Beazley在2010年的PyCon大会上讲过这个问题。正如其他人所说的,对于某些任务来说,使用多线程,尤其是在多核处理器上,可能会比单线程执行同样的任务要慢。Beazley发现,这个问题与多个核心之间的“GIL争夺战”有关:
为了避免GIL的争夺,你可以尝试让任务在不同的进程中运行,而不是在不同的线程中。multiprocessing模块提供了一种方便的方法来实现这一点,特别是因为它的API和线程的API非常相似。
import multiprocessing as mp
import datetime as dt
def work():
t = dt.datetime.now()
print mp.current_process().name, t
i = 0
while i < 100000000:
i+=1
t2 = dt.datetime.now()
print mp.current_process().name, t2, t2-t
if __name__ == '__main__':
print "single process:"
t1 = mp.Process(target=work)
t1.start()
t1.join()
print "multi process:"
t1 = mp.Process(target=work)
t1.start()
t2 = mp.Process(target=work)
t2.start()
t1.join()
t2.join()
产生
single process:
Process-1 2011-12-06 12:34:20.611526
Process-1 2011-12-06 12:34:28.494831 0:00:07.883305
multi process:
Process-3 2011-12-06 12:34:28.497895
Process-2 2011-12-06 12:34:28.503433
Process-2 2011-12-06 12:34:36.458354 0:00:07.954921
Process-3 2011-12-06 12:34:36.546656 0:00:08.048761
PS:正如zeekay在评论中提到的,GIL争夺战对于CPU密集型任务来说是比较严重的,但对于IO密集型任务来说就不是问题了。