threading.Lock()性能问题



我有多个线程:

dispQ = Queue.Queue()
stop_thr_event = threading.Event()
def worker (stop_event):
    while not stop_event.wait(0):
        try:
            job = dispQ.get(timeout=1)
            job.waitcount -= 1
            dispQ.task_done()
        except Queue.Empty, msg:
            continue
# create job objects and put into dispQ here
for j in range(NUM_OF_JOBS):
    j = Job()
    dispQ.put(j)
# NUM_OF_THREADS could be 10-20 ish
running_threads = []
for t in range(NUM_OF_THREADS):
    t1 = threading.Thread( target=worker, args=(stop_thr_event,) )
    t1.daemon = True
    t1.start()
    running_threads.append(t1)

stop_thr_event.set()
for t in running_threads:
    t.join()
上面的代码给了我一些非常奇怪的行为。我最终发现这是由于waitcount递减而没有锁

我给作业类self.thr_lock = threading.Lock()添加了一个属性然后改成

with job.thr_lock:
    job.waitcount -= 1

这似乎修复了奇怪的行为,但看起来它已经降低了性能。

这是预期的吗?有办法优化锁定吗?
一个全局锁比每个作业对象一个锁更好吗?

关于"优化"线程的唯一方法是将处理分解为可以同时执行的块或工作块。这主要意味着做输入或输出(I/O),因为这是解释器释放全局解释器锁的唯一时间,也就是GIL。

实际上,除非满足上述条件,否则由于使用线程的开销,添加线程通常没有任何增益,甚至会有净减速。

如果您对所有共享资源使用单个全局锁,情况可能会更糟,因为它将使程序的某些部分在不需要等待时等待,因为它无法区分需要哪些资源,因此会发生不必要的等待。

你可能会对David Beasley在PyCon 2015的演讲Python Concurrency From the Ground 感兴趣。它涵盖了线程、事件循环和协程。

根据你的代码很难回答你的问题。锁确实有一些固有的成本,没有什么是免费的,但通常是相当小的。如果您的作业非常小,您可能想要考虑将它们"分块",这样相对于每个线程正在完成的工作量,您有更少的获取/释放调用。

一个相关但独立的问题是线程相互阻塞。如果许多线程都在等待同一个锁,您可能会注意到很大的性能问题。在这里,线程处于空闲状态,彼此等待。在某些情况下,这是无法避免的,因为存在一个共享资源,这是一个性能瓶颈。在其他情况下,您可以重新组织代码以避免这种性能损失。

你的示例代码中有一些东西让我觉得它可能与实际应用程序非常不同。首先,示例代码不会在线程之间共享作业对象。如果不共享作业对象,则不需要对其加锁。其次,正如您编写的示例代码一样,在完成之前可能不会清空队列。只要您点击stop_thr_event.set(),它就会退出,并在队列中留下任何剩余的作业,这是故意的吗?

最新更新