python GIL 的多线程示例
A multi-threading example of the python GIL
我已经阅读了很多关于 "bad" 这个 python GIL 业务在编写多线程代码时的情况,但我从未见过一个例子。有人可以给我一个基本示例,说明在使用线程时 GIL 何时会导致问题。
谢谢!
多线程的主要原因之一是程序可以利用多个 CPUs(and/or 个 CPU 上的多个内核)来计算更多操作每秒。但是在 Python 中,GIL 意味着即使你有多个线程同时进行计算,在任何给定时刻,这些线程中只有一个实际上是 运行,因为所有其他线程都是阻塞,等待获取全局解释器锁。这意味着 Python 程序的多线程版本实际上比单线程版本 慢 ,而不是更快,因为一次只有一个线程运行 -- 加上强制每个线程每隔几毫秒等待、获取然后放弃 GIL(循环方式)会产生会计开销。
为了演示这一点,这里有一个玩具 Python 脚本,它生成指定数量的线程,然后作为它的“计算”,每个线程只是不断递增一个计数器,直到 5 秒过去。最后,主线程计算发生的计数器增量的总数并打印总数,让我们衡量在 5 秒内完成了多少“工作”。
import threading
import sys
import time
numSecondsToRun = 5
class CounterThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self._counter = 0
self._endTime = time.time() + numSecondsToRun
def run(self):
# Simulate a computation on the CPU
while(time.time() < self._endTime):
self._counter += 1
if __name__ == "__main__":
if len(sys.argv) < 2:
print "Usage: python counter 5"
sys.exit(5)
numThreads = int(sys.argv[1])
print "Spawning %i counting threads for %i seconds..." % (numThreads, numSecondsToRun)
threads = []
for i in range(0,numThreads):
t = CounterThread()
t.start()
threads.append(t)
totalCounted = 0
for t in threads:
t.join()
totalCounted += t._counter
print "Total amount counted was %i" % totalCounted
..... 这是我在我的电脑上得到的结果(这是一台双核 Mac Mini,启用了超线程,FWIW):
$ python counter.py 1
Spawning 1 counting threads for 5 seconds...
Total amount counted was 14210740
$ python counter.py 2
Spawning 2 counting threads for 5 seconds...
Total amount counted was 10398956
$ python counter.py 3
Spawning 3 counting threads for 5 seconds...
Total amount counted was 10588091
$ python counter.py 4
Spawning 4 counting threads for 5 seconds...
Total amount counted was 11091197
$ python counter.py 5
Spawning 5 counting threads for 5 seconds...
Total amount counted was 11130036
$ python counter.py 6
Spawning 6 counting threads for 5 seconds...
Total amount counted was 10771654
$ python counter.py 7
Spawning 7 counting threads for 5 seconds...
Total amount counted was 10464226
请注意第一次迭代是如何实现最佳性能的(其中只产生了一个工作线程);当多个线程同时 运行 时,计算效率会大幅下降。这表明 Python 中的多线程性能是如何被 GIL 削弱的——用 C(或任何其他没有 GIL 的语言)编写的相同程序在更多线程 运行 下表现出更好的性能,而不是更差(向上当然,直到工作线程的数量与硬件上的内核数量相匹配。
这并不意味着多线程在 Python 中完全没用——它在大多数或所有线程被阻塞等待 I/O 而不是 [= 的情况下仍然有用33=]-绑定。这是因为阻塞等待 I/O 的 Python 线程在等待时不会锁定 GIL,因此在此期间其他线程仍然可以自由执行。但是,如果您需要并行执行计算密集型任务(例如光线追踪或计算 Pi 的所有数字或密码破解或类似任务),那么您将希望使用多进程而不是多线程,或者使用不同的语言没有 GIL。
我已经阅读了很多关于 "bad" 这个 python GIL 业务在编写多线程代码时的情况,但我从未见过一个例子。有人可以给我一个基本示例,说明在使用线程时 GIL 何时会导致问题。
谢谢!
多线程的主要原因之一是程序可以利用多个 CPUs(and/or 个 CPU 上的多个内核)来计算更多操作每秒。但是在 Python 中,GIL 意味着即使你有多个线程同时进行计算,在任何给定时刻,这些线程中只有一个实际上是 运行,因为所有其他线程都是阻塞,等待获取全局解释器锁。这意味着 Python 程序的多线程版本实际上比单线程版本 慢 ,而不是更快,因为一次只有一个线程运行 -- 加上强制每个线程每隔几毫秒等待、获取然后放弃 GIL(循环方式)会产生会计开销。
为了演示这一点,这里有一个玩具 Python 脚本,它生成指定数量的线程,然后作为它的“计算”,每个线程只是不断递增一个计数器,直到 5 秒过去。最后,主线程计算发生的计数器增量的总数并打印总数,让我们衡量在 5 秒内完成了多少“工作”。
import threading
import sys
import time
numSecondsToRun = 5
class CounterThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self._counter = 0
self._endTime = time.time() + numSecondsToRun
def run(self):
# Simulate a computation on the CPU
while(time.time() < self._endTime):
self._counter += 1
if __name__ == "__main__":
if len(sys.argv) < 2:
print "Usage: python counter 5"
sys.exit(5)
numThreads = int(sys.argv[1])
print "Spawning %i counting threads for %i seconds..." % (numThreads, numSecondsToRun)
threads = []
for i in range(0,numThreads):
t = CounterThread()
t.start()
threads.append(t)
totalCounted = 0
for t in threads:
t.join()
totalCounted += t._counter
print "Total amount counted was %i" % totalCounted
..... 这是我在我的电脑上得到的结果(这是一台双核 Mac Mini,启用了超线程,FWIW):
$ python counter.py 1
Spawning 1 counting threads for 5 seconds...
Total amount counted was 14210740
$ python counter.py 2
Spawning 2 counting threads for 5 seconds...
Total amount counted was 10398956
$ python counter.py 3
Spawning 3 counting threads for 5 seconds...
Total amount counted was 10588091
$ python counter.py 4
Spawning 4 counting threads for 5 seconds...
Total amount counted was 11091197
$ python counter.py 5
Spawning 5 counting threads for 5 seconds...
Total amount counted was 11130036
$ python counter.py 6
Spawning 6 counting threads for 5 seconds...
Total amount counted was 10771654
$ python counter.py 7
Spawning 7 counting threads for 5 seconds...
Total amount counted was 10464226
请注意第一次迭代是如何实现最佳性能的(其中只产生了一个工作线程);当多个线程同时 运行 时,计算效率会大幅下降。这表明 Python 中的多线程性能是如何被 GIL 削弱的——用 C(或任何其他没有 GIL 的语言)编写的相同程序在更多线程 运行 下表现出更好的性能,而不是更差(向上当然,直到工作线程的数量与硬件上的内核数量相匹配。
这并不意味着多线程在 Python 中完全没用——它在大多数或所有线程被阻塞等待 I/O 而不是 [= 的情况下仍然有用33=]-绑定。这是因为阻塞等待 I/O 的 Python 线程在等待时不会锁定 GIL,因此在此期间其他线程仍然可以自由执行。但是,如果您需要并行执行计算密集型任务(例如光线追踪或计算 Pi 的所有数字或密码破解或类似任务),那么您将希望使用多进程而不是多线程,或者使用不同的语言没有 GIL。