如何在 Python 中停止循环线程?
- 2025-03-04 08:23:00
- admin 原创
- 81
问题描述:
告诉循环线程停止循环的正确方法是什么?
我有一个相当简单的程序,它在一个单独的类中 ping 指定的主机threading.Thread
。在这个类中,它会休眠 60 秒,然后再次运行,直到应用程序退出。
我想在我的wx.Frame
程序中实现一个“停止”按钮,让循环线程停止。它不需要立即结束线程,只要线程被唤醒,它就可以停止循环。
这是我的threading
课程(注意:我还没有实现循环,但它很可能属于 PingAssets 中的运行方法)
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 {}.
".format(start_time, self.asset, status))
wx.CallAfter(self.window.Logger, msg)
在我的 wxPyhton Frame 中,我从“开始”按钮调用了这个函数:
def CheckAsset(self, asset):
self.count += 1
thread = PingAssets(self.count, asset, self)
self.threads.append(thread)
thread.start()
解决方案 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.
杀灭药丸 - 使用事件
另一种方法是将其用作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()
解决方案 2:
这个问题之前在 Stack 上已经有人问过了。请参阅以下链接:
有没有什么办法可以终止 Python 中的线程?
在一定时间后停止线程
基本上,您只需使用停止函数设置线程,该函数设置线程将检查的标记值。 在您的例子中,您将让循环中的某些东西检查标记值以查看它是否已更改,如果已更改,则循环可以中断并且线程可以终止。
解决方案 3:
我读了 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
解决方案 4:
我采用了不同的方法。我对 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] Unable to open port {}".format(self.port))
print ("[ERROR] {}
{}".format(ex.message, ex.traceback))
self.stopRequest.set()
else:
print ("[INFO] Listening port {}".format(self.port))
self.serial.write("FLOW?
")
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 '
' in char and len(msg) > 1:
char = ''
#~ print('[DATA] {}'.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] Port {} is not open!".format(self.port))
def Stop(self):
if self.isAlive():
self.join()
队列用于向端口发送消息,主循环接收响应。我没有使用 serial.readline() 方法,因为行尾字符不同,而且我发现使用 io 类太麻烦了。
解决方案 5:
取决于您在该线程中运行的内容。如果那是您的代码,那么您可以实现停止条件(请参阅其他答案)。
但是,如果你想要运行别人的代码,那么你应该 fork 并启动一个进程。像这样:
import multiprocessing
proc = multiprocessing.Process(target=your_proc_function, args=())
proc.start()
现在,无论何时您想要停止该进程,都可以向其发送这样的 SIGTERM:
proc.terminate()
proc.join()
而且它并不慢:只需几分之一秒。尽情享受吧 :)
解决方案 6:
我的解决方案是:
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()
解决方案 7:
我发现拥有一个派生自 的类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 秒),但您也可以在构造函数中传递该值。
解决方案 8:
有时你无法控制正在运行的目标。在这种情况下,你可以signal.pthread_kill
发送停止信号。
from signal import pthread_kill, SIGTSTP
from threading import Thread
from itertools import count
from time import sleep
def target():
for num in count():
print(num)
sleep(1)
thread = Thread(target=target)
thread.start()
sleep(5)
pthread_kill(thread.ident, SIGTSTP)
结果
0
1
2
3
4
[14]+ Stopped
扫码咨询,免费领取项目管理大礼包!