【问题标题】:More theading confusion. Why are Semaphores SO slow?更多的混乱。为什么信号量这么慢?
【发布时间】:2015-04-16 05:59:50
【问题描述】:

这个问题是我上一个问题 (Threading in Python - What am I missing?) 的续集。

感谢指出线程问题所在的人。这个问题终于解决了,我启用了信号量,并且惊讶地发现仅仅启用它们就将程序的执行时间从几分之一秒缩短到了 4 秒!哇... 3 秒仅 80000 次迭代似乎慢得可怕,即使考虑到碰撞也是如此。

具体时间:

Without semaphore      0.0167980194092 s
With semaphore         2.76963996887 s

代码如下:

import threading
import time
import random

turnstile_names = ["N", "E", "S", "W"]
count = 0
sem = threading.Semaphore(1)

class Incrementer(threading.Thread):
    def __init__(self, id):
        threading.Thread.__init__(self)
        self.id = id

    def run(self):
        global count, sem
        for i in range(20000):
            sem.acquire()
            count = count + 1
            sem.release()

def main():

    random.seed(507129)
    incrementers = [Incrementer(name) for name in turnstile_names]

    for incrementer in incrementers:
        incrementer.start()

    # We're running!

    for incrementer in incrementers:
        incrementer.join()

    print count
    return 0

if __name__ == '__main__':
    main()

【问题讨论】:

  • 得到80000count总是,没有Semaphore
  • “即使考虑到冲突” - 冲突是轻描淡写,这是顺序执行(增加了开销)。
  • 80,000 个锁在 3 秒内。那是 0.4 毫秒/锁。对于真正工作的线程来说并没有那么糟糕,但是是的,线程同步不是免费的。您可以通过使用线程本地计数器并在最后汇总这些计数器来完全避免这种情况。
  • 我已经使用 Python 3.4.2 在我的 2700K @ 4.6 GHz 上为您的代码计时。每次运行我得到 0.27 秒。然而,这不是你的问题。 GIL 确保在任何时候只有一个线程运行。在 Python 中创建执行繁重 CPU 处理的线程是没有实际意义的。线程仅在您具有 I/O 绑定工作负载时才有用。
  • @Thilo:感谢您的回复。0.​​4ms 在 3GHz 处理器上是永恒的。我只是做了这个例子,因为它是一个经典的信号量演示 - 一个公共计数器的递增是重点。

标签: python multithreading performance semaphore


【解决方案1】:

我无法评论信号量为何如此缓慢,但有一种方法可以通过使用锁而不是信号量来显着加快代码速度:

lock = threading.Lock()

class Incrementer(threading.Thread):
    def __init__(self, id):
        threading.Thread.__init__(self)
        self.id = id

    def run(self):
        global count, lock
        for i in range(20000):
            with lock:
                count = count + 1

无同步:0.02s
信号量同步:2.69s
使用锁:0.36s

也就是说,我认为在略多于两秒半的时间内获取和释放信号量八万次是不错的性能 - 线程同步是有代价的 - 但您应该查看您的选项并选择适合的工具你的用例是最好的,在这种情况下它是一个锁而不是一个信号量,因为你只有一个资源。

【讨论】:

  • 我仍在寻找有关信号量所花费时间的解释。如果锁更好,为什么不用锁实现信号量呢? Mind:我只是从学术的角度来做这件事——考虑到信号量是“经典”的解决方案。有很多选择。此外,鉴于性能上的明显差异,在 Python 文档中提及(或比较)性能可能会很有趣。
【解决方案2】:

信号量在操作系统中实现。它们提供了在其他进程之间进行同步的能力,这是使用锁无法实现的。 因此,它们速度慢的原因现在应该很明显了,每次与信号量交互时,进程都需要对 OS 进行系统调用,上下文切换到内核模式,执行逻辑,然后再次上下文切换回调用进程,如果它没有被阻止。

【讨论】:

  • 如果我没记错的话threading.Semaphores 与操作系统信号量无关(相似性除外)。它们在 Python 的线程模块中实现。操作系统信号量需要定义世界、组和用户权限这一事实也证明了这一点。 (参见 C 的 IPC 库)
  • @jcoppens 你有什么参考吗?
  • 不是马上。但是threading.Semaphoresthreading 库的一部分这一事实是一个非常有力的指标。有一个库semanchuk.com/philip/sysv_ipc 用于在 Python 中使用真实的进程间信号量,它可以与其他信号量进行交互,例如。 C 信号量。如果今天考试后我能找到一些时间,我会再研究一下。
猜你喜欢
  • 1970-01-01
  • 2017-10-31
  • 1970-01-01
  • 1970-01-01
  • 2021-09-03
  • 1970-01-01
  • 2013-02-01
  • 2013-06-05
  • 2019-12-05
相关资源
最近更新 更多