【问题标题】:Python threading outperforms simple while loop OR threading OptimizationPython 线程优于简单的 while 循环或线程优化
【发布时间】:2012-05-29 16:56:34
【问题描述】:

几个小时前,我问了一个关于 Python 多线程的问题。为了了解它的工作原理,我进行了一些实验,以下是我的测试:


使用线程的 Python 脚本:

import threading
import Queue
import time

s = 0;

class ThreadClass(threading.Thread):

    lck = threading.Lock()

    def __init__(self, inQ, outQ):
        threading.Thread.__init__(self)
        self.inQ = inQ
        self.outQ = outQ

    def run(self):
        while True:
            global s
            #print self.getName()+" is running..."
            self.item = self.inQ.get()
            #self.inQ.task_done()
            ThreadClass.lck.acquire()
            s += self.item
            ThreadClass.lck.release()
            #self.inQ.task_done()
            self.outQ.put(self.item)
            self.inQ.task_done()

inQ = Queue.Queue()
outQ = Queue.Queue()

i = 0
n = 1000000

print "putting items to input"
while i<n:
    inQ.put(i)
    i += 1

start_time = time.time()
print "starting threads..."
for i in xrange(10):
    t = ThreadClass(inQ, outQ);
    t.setDaemon(True)
    t.start()


inQ.join()
end_time = time.time()
print "Elapsed time is: %s"%(end_time - start_time)
print s

以下内容具有与简单的 while 循环相同的功能:

import Queue
import time

inQ = Queue.Queue()
outQ = Queue.Queue()

i = 0
n = 1000000
sum = 0

print "putting items to input"
while i<n:
    inQ.put(i)
    i += 1

print "while loop starts..."
start_time = time.time()
while inQ.qsize() > 0:
    item = inQ.get()
    sum += item
    outQ.put(item)
end_time = time.time()

print "Elapsed time is: %s"%(end_time - start_time)
print sum

如果您在您的机器上运行这些程序,您会发现线程比简单的 while 循环慢得多。我对线程有点困惑,想知道线程代码有什么问题。我该如何优化它(在这种情况下),为什么它比 while 循环慢?

【问题讨论】:

  • 您之前的问题的答案有哪些不清楚的地方?总结:线程不会让一切变得更快(如果你在这里做的事情很少并且大部分时间都在锁定和排队,当然不会),即使是这样,CPython 线程也不会使用超过一个核心在任何给定的时间点。
  • 我之前的问题是关于寻找错误,这是关于性能的
  • 哦,对不起,我把你和昨天发布了一个非常相似的问题的另一个用户混淆了,请参阅python multi-threading slower than serial?,它可能很好地回答了你的问题(如果我会投票关闭重复如果有人证实了我的印象)。
  • @torayeff:简单地说,你的线程代码比非线程代码做更多的事情来执行相同的任务。不是所有的事情都可以通过使用线程来优化,而且线程也不仅仅用于优化。
  • @torayeff: task_done() 修改 Queue.join() 方法使用的计数器。 Queue.join() 将阻塞直到任务计数器降为零。如果您注释掉该行,您的所有线程将继续运行/完成,但您的主脚本将在您调用 inQ.join() 时永远阻塞。

标签: python multithreading thread-safety


【解决方案1】:

线程总是很棘手,因为 Python 中的线程是特殊的。

要讨论优化,你必须关注特殊情况,否则没有单一的答案。 我电脑上的初始线程解决方案在 37.11 秒上运行。如果使用局部变量对每个线程的元素求和,然后只在最后加锁,时间会下降到 32.62s。

好的。无线程解决方案在 7.47 秒上运行。伟大的。但是,如果您想在 Python 中对大量数字求和,则只需使用内置函数 sum。因此,如果我们使用没有线程且内置总和的 List,则时间会下降到 0.09 秒。太好了!

为什么?

Python 中的线程受全局解释器锁 (GIL) 的约束。他们永远不会并行运行 Python 代码。它们是真正的线程,但在内部,它们只允许在将 GIL 释放到另一个线程之前运行 X Python 指令。对于非常简单的计算,创建线程、锁定和上下文切换的成本远大于简单计算的成本。所以在这种情况下,开销是计算本身的 5 倍。当您不能使用异步 I/O 或当您有应该同时运行的阻塞函数时,Python 中的线程很有趣。

但是,为什么内置的 sum 比 Python 无线程解决方案更快?内置的 sum 是用 C 实现的,而 Python 循环在性能方面很糟糕。因此使用内置 sum 迭代列表的所有元素要快得多。

总是这样吗?不,这取决于你在做什么。如果您将这些数字写入 n 个不同的文件,线程解决方案可能会有机会,因为 GIL 在 I/O 期间被释放。但即便如此,我们仍需要检查 I/O 缓冲/磁盘同步时间是否不会改变游戏规则。这种细节使最终的答案变得非常困难。所以,如果你想优化某些东西,你必须拥有你必须优化的东西。要在 Python 中对数字列表求和,只需使用内置的 sum。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-15
    相关资源
    最近更新 更多