我有一个使用多个线程的python应用程序,我很好奇在python中等待某些东西的最佳方法,而不会烧掉cpu或锁定GIL。
我的应用程序使用twisted并且我生成一个线程来运行一个长操作,所以我不会踩到反应器线程。这个长操作也会使用twisted的deferToThread产生一些线程来做其他事情,而原始线程想要等待deferreds的结果。
我一直在做的是这个
while self._waiting:
time.sleep( 0.01 )
这似乎破坏了扭曲的PB对象接收消息所以我认为睡眠锁定了GIL。下面的海报进一步调查显示它没有。
有更好的方法可以在不阻塞下面发布的反应器线程或python的情况下等待线程。
答案 0 :(得分:13)
如果您已经在使用Twisted,则永远不需要像这样“等待”。
正如你所描述的那样:
我生成一个线程来运行一个长操作...这个长操作也会使用twisted的deferToThread生成一些线程......
这意味着您从“长操作”线程调用deferToThread
,而不是从主线程(reactor.run()
正在运行的线程)调用。正如Jean-Paul Calderone在评论中已经指出的那样,您只能 从主反应堆线程中调用Twisted API(例如deferToThread
)。
您所看到的锁定是不遵守此规则的常见症状。它与GIL无关,而且与你将Twisted反应堆置于破碎状态这一事实有关。
根据您对程序的松散描述,我尝试编写一个示例程序,完全基于Twisted API执行您所说的内容,通过Twisted生成所有线程并从主反应器线程控制所有线程。
import time
from twisted.internet import reactor
from twisted.internet.defer import gatherResults
from twisted.internet.threads import deferToThread, blockingCallFromThread
def workReallyHard():
"'Work' function, invoked in a thread."
time.sleep(0.2)
def longOperation():
for x in range(10):
workReallyHard()
blockingCallFromThread(reactor, startShortOperation, x)
result = blockingCallFromThread(reactor, gatherResults, shortOperations)
return 'hooray', result
def shortOperation(value):
workReallyHard()
return value * 100
shortOperations = []
def startShortOperation(value):
def done(result):
print 'Short operation complete!', result
return result
shortOperations.append(
deferToThread(shortOperation, value).addCallback(done))
d = deferToThread(longOperation)
def allDone(result):
print 'Long operation complete!', result
reactor.stop()
d.addCallback(allDone)
reactor.run()
请注意,在反应堆停止的allDone
点,您可以启动另一个“长时间操作”并让它重新开始此过程。
答案 1 :(得分:5)
你试过condition variables吗?它们像
一样使用condition = Condition()
def consumer_in_thread_A():
condition.acquire()
try:
while resource_not_yet_available:
condition.wait()
# Here, the resource is available and may be
# consumed
finally:
condition.release()
def produce_in_thread_B():
# ... create resource, whatsoever
condition.acquire()
try:
condition.notify_all()
finally:
condition.release()
条件变量充当锁(acquire
和release
),但它们的主要目的是提供允许wait
为notify
的控制机制 - d或notify_all
- d。
答案 2 :(得分:5)
我最近发现了这个电话
time.sleep( X )
将锁定GIL
整个时间X因此冻结
那个时候的所有python线程
周期。
您发现错误 - 这绝对是不它是如何工作的。您发现此错误信息的来源是什么?
无论如何,那么你澄清(在评论中 - 更好地编辑你的Q!)你正在使用deferToThread
并且你的问题是......:
嗯,是的,我将行动推迟到了 线程并给予扭曲回调。 但是父线程需要等待 对于整个子线程系列来说 完成之前它可以移动到新的 要生成的子线程集
因此,使用带有计数器的对象的方法作为回调 - 将其从0开始,每次延迟到线程时将其递增1并在回调方法中将其递减1。
当回调方法看到递减的计数器已经回到0时,它知道我们已经完成了等待“整个系列的子线程完成”,然后时间已经到了“转向新的”产生子线程的集合“,因此,仅在这种情况下,调用”生成一组新的子线程“函数或方法 - 就这么简单!
E.g。 (从错别字和c开始,因为这是未经测试的代码,只是为了给你这个想法)......:
class Waiter(object):
def __init__(self, what_next, *a, **k):
self.counter = 0
self.what_next = what_next
self.a = a
self.k = k
def one_more(self):
self.counter += 1
def do_wait(self, *dont_care):
self.counter -= 1
if self.counter == 0:
self.what_next(*self.a, **self.k)
def spawn_one_thread(waiter, long_calculation, *a, **k):
waiter.one_more()
d = threads.deferToThread(long_calculation, *a, **k)
d.addCallback(waiter.do_wait)
def spawn_all(waiter, list_of_lists_of_functions_args_and_kwds):
if not list_of_lists_of_functions_args_and_kwds:
return
if waiter is None:
waiter=Waiter(spawn_all, list_of_lists_of_functions_args_and_kwds)
this_time = list_of_list_of_functions_args_and_kwds.pop(0)
for f, a, k in this_time:
spawn_one_thread(waiter, f, *a, **k)
def start_it_all(list_of_lists_of_functions_args_and_kwds):
spawn_all(None, list_of_lists_of_functions_args_and_kwds)
答案 3 :(得分:2)
根据Python源代码,time.sleep()不包含GIL。
http://code.python.org/hg/trunk/file/98e56689c59c/Modules/timemodule.c#l920
请注意使用Py_BEGIN_ALLOW_THREADS
和Py_END_ALLOW_THREADS
,如下所示:
http://docs.python.org/c-api/init.html#thread-state-and-the-global-interpreter-lock
答案 4 :(得分:1)
threading
模块允许您生成一个线程,然后由Thread
对象表示。该对象具有join
方法,您可以使用该方法等待子线程完成。
请参阅http://docs.python.org/library/threading.html#module-threading