7

我正在使用非常标准的 Threading.Event:主线程到达一个循环运行的点:

event.wait(60)

其他阻塞请求,直到回复可用,然后启动:

event.set()

我希望主线程选择 40 秒,但事实并非如此。来自 Python 2.7 源 Lib/threading.py:

# Balancing act:  We can't afford a pure busy loop, so we
# have to sleep; but if we sleep the whole timeout time,
# we'll be unresponsive.  The scheme here sleeps very
# little at first, longer as time goes on, but never longer
# than 20 times per second (or the timeout time remaining).
endtime = _time() + timeout
delay = 0.0005 # 500 us -> initial delay of 1 ms
while True:
   gotit = waiter.acquire(0)
   if gotit:
       break
   remaining = endtime - _time()
   if remaining <= 0:
       break
   delay = min(delay * 2, remaining, .05)
   _sleep(delay)

我们得到的是每 500us 运行一次选择系统调用。这会在具有非常紧密的选择循环的机器上造成明显的负载。

有人可以解释为什么涉及平衡行为以及为什么它与等待文件描述符的线程不同。

其次,有没有更好的方法来实现一个大部分睡眠的主线程而没有如此紧密的循环?

4

2 回答 2

3

我最近遇到了同样的问题,我也追踪到了模块中的这个确切的代码块threading

糟透了。

解决方案是重载线程模块,或迁移到python3已修复这部分实现的地方。

在我的情况下,迁移到 python3 将是一项巨大的努力,所以我选择了前者。我所做的是:

  1. 我创建了一个带有接口的快速.so文件(使用) 。它包括调用相应函数的 python 函数,以及针对. 具体来说,与我们感兴趣的任务最相关的函数是pthread_mutex_timedlockcythonpthreadpthread_mutex_*libpthread
  2. 我创建了一个新threading2模块,(并将import threading代码库中的所有行替换为import threading2)。在threading2中,我重新定义了threading( Lock, Condition, Event) 中的所有相关类,以及Queue我经常使用的类 (QueuePriorityQueue)。该类Lock完全使用函数重新实现pthread_mutex_*,但其余部分要容易得多——我只是将原始类(例如threading.Event)子类化,并重写__init__以创建我的新Lock类型。其余的只是工作。

Lock类型的实现与 中的原始实现非常相似threading,但我基于acquire我在python3'sthreading模块中找到的代码(自然比上述“平衡行为”块简单得多)的新实现。这部分相当容易。

(顺便说一句,在我的案例中,我的大规模多线程进程的速度提高了 30%。甚至超出了我的预期。)

于 2014-03-11T17:51:41.550 回答
2

我完全同意你的看法,这是蹩脚的。

目前,我坚持使用简单的选择调用,没有超时,并监听之前创建的管道。唤醒是通过在管道中写入一个字符来完成的。

从 gunicorn看到这个睡眠唤醒功能。

于 2013-03-24T15:53:10.697 回答