【问题标题】:How to stop a looping thread in Python?如何在 Python 中停止循环线程?
【发布时间】:2013-08-03 18:59:36
【问题描述】:

告诉循环线程停止循环的正确方法是什么?

我有一个相当简单的程序,它在单独的 threading.Thread 类中 ping 指定的主机。在这个类中,它会休眠 60 秒,然后再次运行,直到应用程序退出。

我想在我的wx.Frame 中实现一个“停止”按钮来要求循环线程停止。它不需要立即结束线程,它可以在唤醒后停止循环。

这是我的 threading 类(注意:我还没有实现循环,但它可能属于 PingAssets 中的 run 方法)

class PingAssets(threading.Thread):
    def __init__(self, threadNum, asset, window):
        threading.Thread.__init__(self)
        self.threadNum = threadNum
        self.window = window
        self.asset = asset

    def run(self):
        config = controller.getConfig()
        fmt = config['timefmt']
        start_time = datetime.now().strftime(fmt)
        try:
            if onlinecheck.check_status(self.asset):
                status = "online"
            else:
                status = "offline"
        except socket.gaierror:
            status = "an invalid asset tag."
        msg =("{}: {} is {}.   \n".format(start_time, self.asset, status))
        wx.CallAfter(self.window.Logger, msg)

在我的 wxPyhton 框架中,我从“开始”按钮调用了这个函数:

def CheckAsset(self, asset):
        self.count += 1
        thread = PingAssets(self.count, asset, self)
        self.threads.append(thread)
        thread.start()

【问题讨论】:

    标签: python multithreading wxpython


    【解决方案1】:

    线程可停止函数

    代替子类化threading.Thread,可以修改函数以允许 停在一面旗帜。

    我们需要一个对象,它可以被运行函数访问,我们将标志设置为停止运行。

    我们可以使用threading.currentThread() 对象。

    import threading
    import time
    
    
    def doit(arg):
        t = threading.currentThread()
        while getattr(t, "do_run", True):
            print ("working on %s" % arg)
            time.sleep(1)
        print("Stopping as you wish.")
    
    
    def main():
        t = threading.Thread(target=doit, args=("task",))
        t.start()
        time.sleep(5)
        t.do_run = False
        
    
    if __name__ == "__main__":
        main()
    

    诀窍在于,正在运行的线程可以附加其他属性。解决方案构建 关于假设:

    • 线程有一个属性“do_run”,默认值为True
    • 驱动父进程可以将属性“do_run”分配给启动线程False

    运行代码,我们得到以下输出:

    $ python stopthread.py                                                        
    working on task
    working on task
    working on task
    working on task
    working on task
    Stopping as you wish.
    

    Pill to kill - 使用事件

    其他选择是使用threading.Event 作为函数参数。它是由 默认False,但外部进程可以“设置”(到True)并且函数可以 使用wait(timeout) 函数了解它。

    我们可以wait 零超时,但我们也可以将其用作睡眠计时器(在下面使用)。

    def doit(stop_event, arg):
        while not stop_event.wait(1):
            print ("working on %s" % arg)
        print("Stopping as you wish.")
    
    
    def main():
        pill2kill = threading.Event()
        t = threading.Thread(target=doit, args=(pill2kill, "task"))
        t.start()
        time.sleep(5)
        pill2kill.set()
        t.join()
    

    编辑:我在 Python 3.6 中尝试过这个。 stop_event.wait() 阻止事件(以及 while 循环)直到释放。它不返回布尔值。使用 stop_event.is_set() 代替。

    一粒药停止多个线程

    如果我们必须停止多个线程,则可以更好地看到丸杀死的优势 一次,因为一粒药对所有人都有效。

    doit 根本不会改变,只是 main 处理线程的方式略有不同。

    def main():
        pill2kill = threading.Event()
        tasks = ["task ONE", "task TWO", "task THREE"]
    
        def thread_gen(pill2kill, tasks):
            for task in tasks:
                t = threading.Thread(target=doit, args=(pill2kill, task))
                yield t
    
        threads = list(thread_gen(pill2kill, tasks))
        for thread in threads:
            thread.start()
        time.sleep(5)
        pill2kill.set()
        for thread in threads:
            thread.join()
    

    【讨论】:

    • 有一个问题,假设我们设置了一个像 SIGALRM 这样的内核信号,并且在信号的处理程序中我们想要使用您的方法(pill2kill.set 然后加入)停止进程和线程,然后使用 sys .退出(0)。 1)如果你运行应用程序并等待 n 秒,它工作正常 2)如果你按 ctrl+c,它工作正常 3)但是,如果你按 ctrl+z 然后等待几秒钟然后“fg”到恢复进程,如果 SIGALRM 的 n 秒过去了,进程将停止,但线程继续工作几毫秒。我有一段代码可以证明,你有什么想法吗?
    • Pill to kill 是我正在寻找的东西,因为我正在使用 ThreadPoolExecutor 并且只有 Futures 而不是线程来设置一些属性,例如 do_run
    • 另外,如果您不想等待,您可以使用 is_set 而不是 wait,超时时间为 0
    【解决方案2】:

    我发现有一个派生自threading.Thread 的类来封装我的线程功能很有用。在此类中,您只需在 run() 的覆盖版本中提供您自己的主循环。调用 start() 会安排在单独的线程中调用对象的 run() 方法。

    在主循环中,定期检查是否设置了threading.Event。这样的事件是线程安全的。

    在这个类中,您有自己的join() 方法,它在调用基类的join() 方法之前设置停止事件对象。它可以选择将时间值传递给基类的join() 方法,以确保您的线程在短时间内终止。

    import threading
    import time
    
    class MyThread(threading.Thread):
        def __init__(self, sleep_time=0.1):
            self._stop_event = threading.Event()
            self._sleep_time = sleep_time
            """call base class constructor"""
            super().__init__()
    
        def run(self):
            """main control loop"""
            while not self._stop_event.isSet():
                #do work
                print("hi")
                self._stop_event.wait(self._sleep_time)
    
        def join(self, timeout=None):
            """set stop event and join within a given time period"""
            self._stop_event.set()
            super().join(timeout)
    
    
    if __name__ == "__main__":
        t = MyThread()
        t.start()
    
        time.sleep(5)
    
        t.join(1) #wait 1s max
    

    在检查threading.Event 之前,在主循环内稍作休眠比连续循环占用更少的 CPU 资源。您可以有一个默认的睡眠时间(例如 0.1 秒),但您也可以在构造函数中传递值。

    【讨论】:

      【解决方案3】:

      我的解决办法是:

      import threading, time
      
      def a():
          t = threading.currentThread()
          while getattr(t, "do_run", True):
          print('Do something')
          time.sleep(1)
      
      def getThreadByName(name):
          threads = threading.enumerate() #Threads list
          for thread in threads:
              if thread.name == name:
                  return thread
      
      threading.Thread(target=a, name='228').start() #Init thread
      t = getThreadByName('228') #Get thread by name
      time.sleep(5)
      t.do_run = False #Signal to stop thread
      t.join()
      

      【讨论】:

      • 在代码后面或下方添加 # 并解释该行的用途。只是转储代码让我们“不聪明”;-) 提示:让您的答案成为其他人粘贴到他们的解释器中的有效解决方案。添加额外的代码来做到这一点是大声的。欢迎并享受 SO。审查结束。
      • 当你可以使用name属性时,不需要将线程对象转换为字符串然后解析字符串来获取线程的名称。命名是这段代码的另一个问题,例如,我不明白为什么flex 是线程列表的合理名称。 Here你可以找到更多关于如何改进你的代码的秘诀。
      【解决方案4】:

      取决于您在该线程中运行的内容。 如果这是您的代码,那么您可以实现停止条件(请参阅其他答案)。

      但是,如果您想要运行其他人的代码,那么您应该 fork 并启动一个进程。像这样:

      import multiprocessing
      proc = multiprocessing.Process(target=your_proc_function, args=())
      proc.start()
      

      现在,只要您想停止该进程,就向它发送一个 SIGTERM,如下所示:

      proc.terminate()
      proc.join()
      

      而且它并不慢:几分之一秒。 享受:)

      【讨论】:

        【解决方案5】:

        我有不同的方法。我对 Thread 类进行了子类化,并在构造函数中创建了一个 Event 对象。然后我编写了自定义的 join() 方法,它首先设置这个事件,然后调用它自己的父版本。

        这是我的课程,我在 wxPython 应用程序中用于串口通信:

        import wx, threading, serial, Events, Queue
        
        class PumpThread(threading.Thread):
        
            def __init__ (self, port, queue, parent):
                super(PumpThread, self).__init__()
                self.port = port
                self.queue = queue
                self.parent = parent
        
                self.serial = serial.Serial()
                self.serial.port = self.port
                self.serial.timeout = 0.5
                self.serial.baudrate = 9600
                self.serial.parity = 'N'
        
                self.stopRequest = threading.Event()
        
            def run (self):
                try:
                    self.serial.open()
                except Exception, ex:
                    print ("[ERROR]\tUnable to open port {}".format(self.port))
                    print ("[ERROR]\t{}\n\n{}".format(ex.message, ex.traceback))
                    self.stopRequest.set()
                else:
                    print ("[INFO]\tListening port {}".format(self.port))
                    self.serial.write("FLOW?\r")
        
                while not self.stopRequest.isSet():
                    msg = ''
                    if not self.queue.empty():
                        try:
                            command = self.queue.get()
                            self.serial.write(command)
                        except Queue.Empty:
                            continue
        
                    while self.serial.inWaiting():
                        char = self.serial.read(1)
                        if '\r' in char and len(msg) > 1:
                            char = ''
                            #~ print('[DATA]\t{}'.format(msg))
                            event = Events.PumpDataEvent(Events.SERIALRX, wx.ID_ANY, msg)
                            wx.PostEvent(self.parent, event)
                            msg = ''
                            break
                        msg += char
                self.serial.close()
        
            def join (self, timeout=None):
                self.stopRequest.set()
                super(PumpThread, self).join(timeout)
        
            def SetPort (self, serial):
                self.serial = serial
        
            def Write (self, msg):
                if self.serial.is_open:
                    self.queue.put(msg)
                else:
                    print("[ERROR]\tPort {} is not open!".format(self.port))
        
            def Stop(self):
                if self.isAlive():
                    self.join()
        

        队列用于向端口发送消息,主循环取回响应。我没有使用 serial.readline() 方法,因为 end-line char 不同,我发现 io 类的使用太麻烦了。

        【讨论】:

          【解决方案6】:

          我阅读了 Stack 上的其他问题,但我对跨类交流仍然有些困惑。这是我的处理方法:

          我使用一个列表来保存我的 wxFrame 类的 __init__ 方法中的所有线程:self.threads = []

          按照How to stop a looping thread in Python? 中的建议,我在线程类中使用了一个信号,在初始化线程类时设置为True

          class PingAssets(threading.Thread):
              def __init__(self, threadNum, asset, window):
                  threading.Thread.__init__(self)
                  self.threadNum = threadNum
                  self.window = window
                  self.asset = asset
                  self.signal = True
          
              def run(self):
                  while self.signal:
                       do_stuff()
                       sleep()
          

          我可以通过遍历我的线程来停止这些线程:

          def OnStop(self, e):
                  for t in self.threads:
                      t.signal = False
          

          【讨论】:

            【解决方案7】:

            以前在 Stack 上已经问过这个问题。请参阅以下链接:

            基本上,您只需要使用停止函数设置线程,该函数设置线程将检查的标记值。在您的情况下,您将在循环中检查标记值以查看它是否已更改,如果已更改,则循环可能会中断并且线程可能会死亡。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2018-11-19
              • 1970-01-01
              • 2021-12-10
              • 2012-03-18
              • 1970-01-01
              • 1970-01-01
              • 2013-11-19
              相关资源
              最近更新 更多