让我的代码在后台处理耗时的函数调用

2 投票
3 回答
979 浏览
提问于 2025-04-15 12:16

我在代码中有一些函数执行得很慢,等它们返回结果的时候,我已经不需要这个结果了。我想在这些慢函数返回之前,先执行脚本中的下一行代码。具体来说,这些函数是通过USB向另一个系统发送命令(使用一个C++库和SWIG),等那个系统完成任务后,它会返回一个“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

你可以使用一个叫做 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()

不过,使用没有“主”线程的系统时,很难判断什么时候该调用“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 的主要区别在于,如果主线程中发生了更多的“事情”——比如其他定时事件触发、其他网络连接有流量、图形界面中按钮被点击——这些工作会无缝进行,因为 deferLateryield d 并不会停止整个线程,它们只是暂停了“主” inlineCallbacks 协程。

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文档

关于多线程,有一点要提醒你:这可能会很难。非常难。调试多线程软件可能会让你体验到作为软件开发者最糟糕的事情。

所以,在你深入研究可能出现的死锁和竞争条件之前,一定要确认将你的同步USB交互转换为异步交互是有意义的。特别是,要确保任何依赖于异步代码的代码是在异步代码完成后执行的(可以通过回调方法或类似的方式来实现)。

4

除非使用SWIG生成的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及以后的版本中,multiprocessing已经包含在标准库里,但对于2.5和2.4版本,可以单独下载并安装。

补充说明:提问者显然想做的事情比这更复杂,他在评论中解释说:

“我遇到了一堆错误,最后是:“pickle.PicklingError: Can't pickle <type 'PySwigObject'>: it's not found as __builtin__.PySwigObject”。这个问题能在不重组我所有代码的情况下解决吗?这个进程是从绑定到我wxPython界面按钮的方法中调用的。”

multiprocessing确实需要对对象进行序列化(也就是“打包”),以便跨进程传递;我不太确定这里涉及到的SWIG对象具体是什么,但除非你能找到一种方法来序列化和反序列化它,并将其注册到copy_reg模块,否则你需要避免在进程之间传递它(让SWIG对象只由一个进程拥有和使用,不要让它们成为模块全局对象,特别是在__main__中,进程之间通过不包含SWIG对象的其他对象进行通信,使用Queue.Queue等)。

如果早期的错误(如果和你报告的“最后的”错误不同)可能更重要,但我无法猜测,因为没有看到它们。

撰写回答