问题:由于Python使用了“全局解释器锁(GIL)”,Python能够同时运行其单独的线程吗?
信息:
阅读了这篇文章后,我对Python是否能够利用多核处理器有些不确定。尽管Python非常出色,但它缺乏这样一个强大的能力似乎感觉很奇怪。所以我有点不确定,决定在这里询问。如果我编写一个多线程程序,它能够在多个核心上同时执行吗?
问题:由于Python使用了“全局解释器锁(GIL)”,Python能够同时运行其单独的线程吗?
信息:
阅读了这篇文章后,我对Python是否能够利用多核处理器有些不确定。尽管Python非常出色,但它缺乏这样一个强大的能力似乎感觉很奇怪。所以我有点不确定,决定在这里询问。如果我编写一个多线程程序,它能够在多个核心上同时执行吗?
multiprocessing
, celery
或mpi4py
之类的东西将并行工作拆分到另一个进程中;numpy
可以完成繁重的工作而不保留GIL,因此其他python线程可以继续。您也可以以这种方式使用ctypes
库。Python中的线程无法充分利用多个核心。这是由于在cPython的内部实现细节中存在全局解释器锁(GIL)的原因,而几乎肯定您使用的就是cPython。
解决方法是使用multiprocessing
模块http://www.python.org/dev/peps/pep-0371/,该模块是为此目的开发的。
文档:http://docs.python.org/library/multiprocessing.html
(或使用并行语言。)
CPython(Python的经典和普遍实现)不能同时执行多个线程的Python字节码。这意味着计算密集型程序只会使用一个核心。I/O操作和在C扩展中发生的计算(例如numpy)可以同时进行。
Python的其他实现(如Jython或PyPy)可能有所不同,我对它们的细节了解较少。
通常建议使用许多进程而不是许多线程。
典型结果:
. 启动 4000 个 IO bound 线程循环
. 顺序运行时间: 39.15 秒
. 4 个线程并行运行时间: 18.19 秒
. 2 个线程并行运行时间翻倍: 20.61 秒
典型结果:
. 启动 1000000 个仅 CPU 的线程循环
. 顺序运行时间: 9.39 秒
. 4 个线程并行运行时间: 10.19 秒
. 2 个线程并行运行时间翻倍: 9.58 秒
典型结果:
. 启动 4000 个 IO bound 进程循环
. 顺序运行时间: 39.74 秒
. 4 个进程并行运行时间: 17.68 秒
. 2 个进程并行运行时间翻倍: 20.68 秒
典型结果:
. 启动 1000000 个仅 CPU 的进程循环
. 顺序运行时间: 9.24 秒
. 4 个进程并行运行时间: 2.59 秒
. 2 个进程并行运行时间翻倍: 4.76 秒
compare_io_multiproc.py:
#!/usr/bin/env python3
# Compare single proc vs multiple procs execution for io bound operation
"""
Typical Result:
Starting 4000 cycles of io-bound processing
Sequential - run time: 39.74 seconds
4 procs Parallel - run time: 17.68 seconds
2 procs Parallel twice - run time: 20.68 seconds
"""
import time
import multiprocessing as mp
# one thousand
cycles = 1 * 1000
def t():
with open('/dev/urandom', 'rb') as f:
for x in range(cycles):
f.read(4 * 65535)
if __name__ == '__main__':
print(" Starting {} cycles of io-bound processing".format(cycles*4))
start_time = time.time()
t()
t()
t()
t()
print(" Sequential - run time: %.2f seconds" % (time.time() - start_time))
# four procs
start_time = time.time()
p1 = mp.Process(target=t)
p2 = mp.Process(target=t)
p3 = mp.Process(target=t)
p4 = mp.Process(target=t)
p1.start()
p2.start()
p3.start()
p4.start()
p1.join()
p2.join()
p3.join()
p4.join()
print(" 4 procs Parallel - run time: %.2f seconds" % (time.time() - start_time))
# two procs
start_time = time.time()
p1 = mp.Process(target=t)
p2 = mp.Process(target=t)
p1.start()
p2.start()
p1.join()
p2.join()
p3 = mp.Process(target=t)
p4 = mp.Process(target=t)
p3.start()
p4.start()
p3.join()
p4.join()
print(" 2 procs Parallel twice - run time: %.2f seconds" % (time.time() - start_time))
compare_cpu_multiproc.py
#!/usr/bin/env python3
# Compare single proc vs multiple procs execution for cpu bound operation
"""
Typical Result:
Starting 1000000 cycles of cpu-only processing
Sequential run time: 9.24 seconds
4 procs Parallel - run time: 2.59 seconds
2 procs Parallel twice - run time: 4.76 seconds
"""
import time
import multiprocessing as mp
# one million
cycles = 1000 * 1000
def t():
for x in range(cycles):
fdivision = cycles / 2.0
fcomparison = (x > fdivision)
faddition = fdivision + 1.0
fsubtract = fdivision - 2.0
fmultiply = fdivision * 2.0
if __name__ == '__main__':
print(" Starting {} cycles of cpu-only processing".format(cycles))
start_time = time.time()
t()
t()
t()
t()
print(" Sequential run time: %.2f seconds" % (time.time() - start_time))
# four procs
start_time = time.time()
p1 = mp.Process(target=t)
p2 = mp.Process(target=t)
p3 = mp.Process(target=t)
p4 = mp.Process(target=t)
p1.start()
p2.start()
p3.start()
p4.start()
p1.join()
p2.join()
p3.join()
p4.join()
print(" 4 procs Parallel - run time: %.2f seconds" % (time.time() - start_time))
# two procs
start_time = time.time()
p1 = mp.Process(target=t)
p2 = mp.Process(target=t)
p1.start()
p2.start()
p1.join()
p2.join()
p3 = mp.Process(target=t)
p4 = mp.Process(target=t)
p3.start()
p4.start()
p3.join()
p4.join()
print(" 2 procs Parallel twice - run time: %.2f seconds" % (time.time() - start_time))
以下是可以占用ubuntu 14.04上所有4个核心的示例代码,该系统使用的是Python 2.7 64位版本。
import time
import threading
def t():
with open('/dev/urandom') as f:
for x in xrange(100):
f.read(4 * 65535)
if __name__ == '__main__':
start_time = time.time()
t()
t()
t()
t()
print "Sequential run time: %.2f seconds" % (time.time() - start_time)
start_time = time.time()
t1 = threading.Thread(target=t)
t2 = threading.Thread(target=t)
t3 = threading.Thread(target=t)
t4 = threading.Thread(target=t)
t1.start()
t2.start()
t3.start()
t4.start()
t1.join()
t2.join()
t3.join()
t4.join()
print "Parallel run time: %.2f seconds" % (time.time() - start_time)
结果:
$ python 1.py
Sequential run time: 3.69 seconds
Parallel run time: 4.82 seconds
import time
import threading
def t():
with open('/dev/urandom', 'rb') as f:
for x in range(100):
f.read(4 * 65535)
if __name__ == '__main__':
start_time = time.time()
t()
t()
t()
t()
print("Sequential run time: %.2f seconds" % (time.time() - start_time))
start_time = time.time()
t1 = threading.Thread(target=t)
t2 = threading.Thread(target=t)
t3 = threading.Thread(target=t)
t4 = threading.Thread(target=t)
t1.start()
t2.start()
t3.start()
t4.start()
t1.join()
t2.join()
t3.join()
t4.join()
print("Parallel run time: %.2f seconds" % (time.time() - start_time))
python3 t.py
Sequential run time: 2.10 seconds
Parallel run time: 1.41 seconds
对我而言,使用并行运行更加快速。
线程共享一个进程,进程在核心上运行。但是你可以使用Python的multiprocessing模块在不同的进程中调用函数并利用其他核心,或者你可以使用subprocess模块运行你的代码和非Python代码。