在Twisted中使用线程的正确方法?

9 投票
2 回答
8899 浏览
提问于 2025-04-15 19:08

我需要写一个简单的应用程序,运行两个线程:

  • 线程1:按时间间隔运行,比如每分钟一次
  • 线程2:就是一个普通的 while True 循环,做一些事情

如果不是需要按时间间隔运行,我根本不会考虑使用 twisted,但简单的 sleep(60) 方式不够好,像这样的结构:

l = task.LoopingCall(timed_thread)
l.start(60.0)
reactor.run()

看起来真的很简单,可以实现我想要的功能。

现在,我该如何“正确”地添加另一个线程呢?

我看到两种选择:

  • 使用 threading 库,运行两个“python 线程”,一个执行我的 while 循环,另一个运行 reactor.run()。但是谷歌似乎不太推荐这种方法,建议使用 twisted 的线程。
  • 使用 twisted 的线程。我试过这个,但感觉有点笨拙。

这是我想到的:

def timed_thread():
    print 'i will be called every 1 minute'
    return

def normal_thread():
    print 'this is a normal thread'
    time.sleep(30)
    return

l = task.LoopingCall(timed_thread)
l.start(60.0)
reactor.callInThread(normal_thread)
reactor.run()

这似乎可以工作,但!我无法停止这个应用程序。如果我按 ^C,它不会有任何反应(如果没有 'callInThread',它就会像你预期的那样停止)。按 ^Z 会返回到 shell,如果我接着执行 'kill %1',似乎会杀掉进程(shell 报告了这一点),但“正常”的线程还是在运行。用 kill PID 也无法解决,唯一的办法是 kill -9。真的很奇怪。

所以,我到底做错了什么?在 twisted 中实现两个线程是正确的方法吗?我是不是不该用 twisted?还有什么其他“标准”的方法可以实现定时调用?(“标准”是指我可以用 easy_install 或 yum install 安装的,我不想去下载一些随机网页上的脚本)。

2 个回答

5

你没有解释为什么在这里需要线程。如果你解释了,我可能能告诉你其实你不需要它们。;)

抛开这些不谈,我可以确认你对事情的基本理解是正确的。不过,有一个可能的误解我可以澄清,就是“python线程”和“Twisted线程”其实没有什么不同。它们是一样的。Python提供了一个线程库,而Twisted的所有线程API都是基于Python的线程库实现的。只是API的形式不同而已。

关于关闭程序,你有两个选择。

  • 直接使用Python的线程API来启动一个永远运行的线程,并把这个线程设置为守护线程。这样即使守护线程还在运行,你的程序也可以退出。不过,这种方法可能会有个问题,就是某些版本的Python在关闭时会因为守护线程而崩溃。
  • 使用Twisted的API或者标准库的线程API来创建你的线程,同时添加一个Twisted的关闭钩子,使用reactor.addSystemEventTrigger('before', 'shutdown', f)。在这个钩子里,你可以和工作线程进行沟通,告诉它要关闭。例如,你可以在Twisted线程和工作线程之间共享一个threading.Event,然后在钩子里调用set。工作线程可以定期检查这个事件是否被设置,如果发现被设置了就退出。除了不会崩溃,这种方法还有一个好处,就是可以让你在程序退出之前,在工作线程里运行一些清理或收尾的代码。
2

假设你的主程序运行得比较顺畅,不会卡住:

import random
from twisted.internet import task

class MyProcess:
  def __init__(self):
    self.stats = []
    self.lp = None
  def myloopingCall(self):
    print "I have %s stats" % len(self.stats)
  def myMainFunction(self,reactor):
    self.stats.append(random.random())
    reactor.callLater(0,self.myMainFunction,reactor)
  def start(self,reactor):
    self.lp = task.LoopingCall(self.myloopingCall)
    self.lp.start(2)
    reactor.callLater(0,self.myMainFunction,reactor)
  def stop(self):
    if self.lp is not None:
      self.lp.stop()
    print "I'm done"

if __name__ == '__main__':
  myproc = MyProcess()
  from twisted.internet import reactor
  reactor.callWhenRunning(myproc.start,reactor)
  reactor.addSystemEventTrigger('during','shutdown',myproc.stop)
  reactor.callLater(10,reactor.stop)
  reactor.run()
$ python bleh.py
I have 0 stats
I have 33375 stats
I have 66786 stats
I have 100254 stats
I have 133625 stats
I'm done

撰写回答