如何在Python中停止循环线程?


73

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

我有一个相当简单的程序,可以在单独的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()

Answers:


118

螺纹可停止功能

除了子类化之外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
    t.join()

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.

药丸杀死-使用事件

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

我们可以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()

2
好答案!帮助了我很多
本尼

有一个问题,想象一下,我们设置了一个像SIGALRM这样的内核信号,并且在该信号的处理程序中,我们想要使用您的方法(pill2kill.set然后加入)和sys.exit(0)来停止进程和线程。1)如果您运行该应用程序并等待n秒钟,则可以正常运行2)如果您按ctrl + c,则可以正常运行3)但是,如果您按ctrl + z然后等待几秒钟,然后“ fg”如果已超过SIGALRM的n秒,将恢复该过程,但该过程将停止,但线程将继续工作几毫秒。我有一段代码可以证明这一点,您对此有任何想法吗?
KOrrosh Sh

当我使用ThreadPoolExecutor时,我一直在寻找要杀死的do_run
药,

另外,如果您不想等待,可以使用is_set代替wait超时为0的文件
-Genarito


14

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

我使用列表将所有线程保存在__init__wxFrame类的方法中:self.threads = []

如“如何在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

2

我有不同的方法。我对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()方法,因为使用了不同的终端字符,而且我发现io类的用法很麻烦。


0

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

但是,如果您要运行其他人的代码,则应分叉并启动一个过程。像这样:

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

现在,只要您想停止该过程,就向它发送如下的SIGTERM:

proc.terminate()
proc.join()

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


0

我的解决方案是:

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()

1
在代码的后面或下面添加#并说明该行的用途。只是转储代码会使我们“不聪明” ;-)提示:使您的答案成为可行的解决方案,以便其他人粘贴到他们的解释器中。添加额外的代码可以做到这一点。欢迎并享受SO。审查结束。
ZF007

当可以使用Thread对象的属性时,无需将Thread对象转换为字符串,然后解析该字符串以获取线程的名称name。命名是此代码的另一个问题,例如,我不明白为什么flex线程列表是一个合理的名称。在这里,您可以找到有关如何改进代码的更多秘诀。
cl0ne
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.