Python GIL的多线程示例

6

我已经阅读了关于编写多线程代码时“糟糕”的Python GIL问题的很多内容,但是我从未看到过例子。请问有人可以给我一个基本的示例,说明在使用线程时GIL会引起什么问题。

谢谢!

1个回答

14
多线程的主要原因之一是为了让程序利用多个CPU(和/或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)得到的测试结果:

$ 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而不是CPU绑定的情况下,它仍然很有用。这是因为在等待I/O的过程中,被阻塞的Python线程不会保持GIL锁定状态,因此在此期间其他线程仍然可以自由执行。但是,如果您需要并行化计算密集型任务(例如光线跟踪或计算Pi的所有数字或破解代码等),则需要使用多个进程而不是多个线程,或使用另一种没有GIL的语言。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接