让我的代码在后台处理耗时的函数调用
我在代码中有一些函数执行得很慢,等它们返回结果的时候,我已经不需要这个结果了。我想在这些慢函数返回之前,先执行脚本中的下一行代码。具体来说,这些函数是通过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 个回答
你可以使用一个叫做 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 的主要区别在于,如果主线程中发生了更多的“事情”——比如其他定时事件触发、其他网络连接有流量、图形界面中按钮被点击——这些工作会无缝进行,因为 deferLater
和 yield d
并不会停止整个线程,它们只是暂停了“主” inlineCallbacks
协程。
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交互转换为异步交互是有意义的。特别是,要确保任何依赖于异步代码的代码是在异步代码完成后执行的(可以通过回调方法或类似的方式来实现)。
除非使用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等)。
如果早期的错误(如果和你报告的“最后的”错误不同)可能更重要,但我无法猜测,因为没有看到它们。