在后台函数调用中处理我的代码需要很长时间才能完成

时间:2009-06-15 22:09:02

标签: python multithreading

我的代码中的某些函数需要很长时间才能返回。我不需要返回值,我想在慢函数返回之前执行脚本中的下一行代码。更准确地说,这些函数通过USB将命令发送到另一个系统(通过带有SWIG的C ++库),一旦另一个系统完成任务,它就会返回一个“OK”值。我在以下示例中重现了该问题。如何让“tic”和“toc”一个接一个地打印出来而没有任何延迟?我想解决方案涉及线程,但我不太熟悉它们。谁能告诉我一个简单的方法来解决这个问题?

from math import sqrt
from time import sleep

def longcalc():
    total = 1e6
    for i in range(total):
        r = sqrt(i)
    return r

def longtime():
    #Do stuff here
    sleep(1)
    return "sleep done"

print "tic"
longcalc()
print "toc"
longtime()
print "tic"

3 个答案:

答案 0 :(得分:4)

除非SWIGged C ++代码专门设置为在长时间延迟之前释放GIL(全局解释器锁)并在返回Python之前重新获取它,否则多线程在实践中可能不会非常有用。您可以尝试使用multiprocessing

from multiprocessing import Process

if __name__ == '__main__':
    print "tic"
    Process(target=longcalc).start()
    print "toc"
    Process(target=longtime).start()
    print "tic"

多处理在Python 2.6及更高版本的标准库中,但可以单独downloaded并安装用于版本2.5和2.4。

编辑:提问者当然正在尝试做一些比这更复杂的事情,并在评论中解释: msgstr“”“我得到了一堆以"pickle.PicklingError: Can't pickle <type 'PySwigObject'>: it's not found as __builtin__.PySwigObject"结尾的错误。这可以在不重新组织我的所有代码的情况下解决吗?从一个绑定到我的wxPython接口的按钮的方法内部调用进程。”“”

multiprocessing确实需要挑选对象来跨越进程边界;不确定这里涉及到什么SWIGged对象,但是,除非你能找到一种方法来序列化和反序列化它,并将其注册到copy_reg module,你需要避免将它传递到边界(使SWIGged对象拥有和由单个进程使用,不要将它们作为模块全局对象,特别是在__main__中,通过不包含SWIGged对象的对象与Queue.Queue进行通信等。)

早期错误(如果与您报告的“以”结尾“不一致)可能实际上更重要,但如果没有看到它们我就无法猜测。

答案 1 :(得分:1)

from threading import Thread
# ... your code    

calcthread = Thread(target=longcalc)
timethread = Thread(target=longtime)

print "tic"
calcthread.start()
print "toc"
timethread.start()
print "tic"

有关python中多线程的更多信息,请查看python threading docs

关于多线程的警告:它可能很难。 非常很难。调试多线程软件可能会导致您作为软件开发人员遇到的一些最糟糕的体验。

因此,在深入了解潜在的死锁和竞争条件之前,请务必确保将同步USB交互转换为异步USB交互是有意义的。具体来说,确保任何依赖于异步代码的代码在完成后执行(通过callback method或类似的东西)。

答案 2 :(得分:0)

您可以使用Future,它不包含在标准库中,但实现起来非常简单:

from threading import Thread, Event

class Future(object):
    def __init__(self, thunk):
        self._thunk = thunk
        self._event = Event()
        self._result = None
        self._failed = None
        Thread(target=self._run).start()

    def _run(self):
        try:
            self._result = self._thunk()
        except Exception, e:
            self._failed = True
            self._result = e
        else:
            self._failed = False
        self._event.set()

    def wait(self):
        self._event.wait()
        if self._failed:
            raise self._result
        else:
            return self._result

你会像这样使用这个特定的实现:

import time

def work():
    for x in range(3):
        time.sleep(1)
        print 'Tick...'
    print 'Done!'
    return 'Result!'

def main():
    print 'Starting up...'
    f = Future(work)
    print 'Doing more main thread work...'
    time.sleep(1.5)
    print 'Now waiting...'
    print 'Got result: %s' % f.wait()

不幸的是,当使用没有“主”线程的系统时,很难判断何时调用“等待”;在你绝对需要答案之前,你显然不想停止处理。

使用Twisted,您可以使用deferToThread,它允许您返回主循环。 Twisted中的惯用等效代码将是这样的:

import time

from twisted.internet import reactor
from twisted.internet.task import deferLater
from twisted.internet.threads import deferToThread
from twisted.internet.defer import inlineCallbacks

def work():
    for x in range(3):
        time.sleep(1)
        print 'Tick...'
    print 'Done!'
    return 'Result!'

@inlineCallbacks
def main():
    print 'Starting up...'
    d = deferToThread(work)
    print 'Doing more main thread work...'
    yield deferLater(reactor, 1.5, lambda : None)
    print "Now 'waiting'..."
    print 'Got result: %s' % (yield d)

虽然为了实际启动反应堆并在完成时退出,你也需要这样做:

reactor.callWhenRunning(
    lambda : main().addCallback(lambda _: reactor.stop()))
reactor.run()

与Twisted的主要区别在于,如果在主线程中发生更多“东西” - 其他定时事件触发,其他网络连接获得流量,按钮在GUI中被点击 - 这项工作将无缝地发生,因为{{1并且deferLater实际上并没有停止整个线程,它们只会暂停“主”yield d协程。