14

我试图弄清楚python中的多线程编程。这是我想比较串行和并行速度的简单任务。

import threading
import Queue
import time
import math

def sinFunc(offset, n):
  result = []
  for i in range(n):
    result.append(math.sin(offset + i * i))
  return result

def timeSerial(k, n):
  t1 = time.time()    
  answers = []
  for i in range(k):
    answers.append(sinFunc(i, n))
  t2 = time.time()
  print "Serial time elapsed: %f" % (t2-t1)

class Worker(threading.Thread):

  def __init__(self, queue, name):
    self.__queue = queue
    threading.Thread.__init__(self)
    self.name = name

  def process(self, item):
    offset, n = item
    self.__queue.put(sinFunc(offset, n))
    self.__queue.task_done()
    self.__queue.task_done()

  def run(self):
    while 1:
        item = self.__queue.get()
        if item is None:
            self.__queue.task_done()
            break
        self.process(item)

def timeParallel(k, n, numThreads):
  t1 = time.time()    
  queue = Queue.Queue(0)
  for i in range(k):
    queue.put((i, n))
  for i in range(numThreads):
    queue.put(None)    
  for i in range(numThreads):
    Worker(queue, i).start()
  queue.join()
  t2 = time.time()
  print "Serial time elapsed: %f" % (t2-t1)

if __name__ == '__main__':

  n = 100000
  k = 100
  numThreads = 10

  timeSerial(k, n)
  timeParallel(k, n, numThreads)

#Serial time elapsed: 2.350883
#Serial time elapsed: 2.843030

有人可以向我解释发生了什么吗?我习惯了 C++,使用该模块的类似版本可以看到我们期望的加速。

4

3 回答 3

27

其他答案提到 GIL 是 cpython 中的问题。但我觉得有一些缺失的信息。在线程中运行的代码受 CPU 限制的情况下,这将导致性能问题。在您的情况下,是的,在线程中进行许多计算很可能会导致性能显着下降。

但是,如果您正在执行更多 IO 绑定的操作,例如从网络应用程序中的许多套接字读取,或调用子进程,您可以从线程中获得性能提升。上面代码的一个简单示例是向 shell 添加一个愚蠢的简单调用:

import os

def sinFunc(offset, n):
  result = []
  for i in xrange(n):
    result.append(math.sin(offset + i * i))
  os.system("echo 'could be a database query' >> /dev/null; sleep .1")
  return result

那个调用可能就像在文件系统上等待一样。但是你可以看到,在这个例子中,线程将开始被证明是有益的,因为当线程等待 IO 并且其他线程将继续处理时,GIL 可以被释放。即便如此,当更多线程开始被创建和同步它们的开销所抵消时,仍然存在一个最佳点。

对于 CPU 绑定代码,您将使用多处理

来自文章:http ://www.informit.com/articles/article.aspx?p=1850445&seqNum=9

...线程更适合 I/O 绑定应用程序(I/O 释放 GIL,允许更多并发)...

关于线程与进程的类似问题参考:
https ://stackoverflow.com/a/1227204/496445
https://stackoverflow.com/a/990436/496445

于 2012-05-28T19:17:55.707 回答
6

Python 有一个严重的线程问题。基本上,向 Python 应用程序添加线程几乎总是无法使其更快,有时会使其变慢。

这是由于Global Interpreter Lock或 GIL 造成的。

这是有关它的博客文章,其中包括有关该主题的演讲。

绕过此限制的一种方法是使用进程而不是线程。多处理模块使这变得更容易。

于 2012-05-28T18:39:52.107 回答
0

用 C 编写的 Python 库可以随意获取/释放全局解释器锁 (GIL)。那些不使用 Python 对象的人可以释放 GIL,以便其他线程可以查看,但是我相信数学库一直使用 Python 对象,因此有效地将 math.sin 序列化。由于锁定/解锁是一种开销,因此 Python 线程比进程慢并不罕见。

于 2012-05-28T18:48:02.860 回答