先上正确的代码
参考 https://blog.csdn.net/vinsuan1993/article/details/78158589/
import threading
import time
import inspect
import ctypes
class KillableThread(threading.Thread):
def __init__(self, *args, **kw):
super(KillableThread, self).__init__(*args, **kw)
def _async_raise(tid, exctype):
"""raises the exception, performs cleanup if needed"""
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
# """if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect"""
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
def kill(self):
KillableThread._async_raise(self.ident, SystemExit)
# 循环输出s,t次,每0.6秒一次
def say(s,t):
for i in range(t):
print(s)
time.sleep(0.6)
if __name__ == "__main__":
thread1 = KillableThread(target=say, args=("Say 3 times", 3))
thread1.start()
thread1.join()
print("Alive? ", thread1.is_alive())
thread2 = KillableThread(target=say, args=("Say 999 times", 999))
thread2.start()
thread2.join(2)
print("Alive? ", thread2.is_alive())
thread2.kill()
print("Still alive? ", thread2.is_alive())
# 查看线程数量
while True:
thread_num = len(threading.enumerate())
print("线程数量是%d" % thread_num)
if thread_num <= 1:
break
time.sleep(1)
print("Still alive? ", thread2.is_alive())
输出为
Say 3 times
Say 3 times
Say 3 times
Alive? False
Say 999 times
Say 999 times
Say 999 times
Say 999 times
Alive? True
Still alive? True
线程数量是2
线程数量是1
Still alive? False
------ 分割线以下分析是错的 ------
后面的代码是错误的,错误在于setDaemon是让被设置的线程随主线程一起结束,而不是父线程,我不清楚这样有什么意义。之所以没有发现下面代码是错的,是因为正好主线程后面就结束了,如果让主线程继续空转就会看到999那个线程还在输出
Python中的threading.Thread并没有提供kill方法杀死尚未结束的线程,但是提供了setDaemon方法设置守护线程,守护线程会在父线程结束时自动结束。
注意父线程是调用线程start()的线程,而不是创建threading.Thread对象的线程,下面的代码演示了通过setDaemon方法让线程超时自动结束。
import threading
import time
def start_thread_with_timeout(target, secs:int, args=None):
th = threading.Thread(target=target, args=args)
timeout = True
def daemon_thread():
'''
对于嵌套定义的函数,当Python发现读取一个不存在的变量时,
会向外层去找,而当给一个变量赋值时,若函数内不存在此变量,
优先创建而不向外找,所以此处timeout必须声明为nonlocal,
而对th的声明是可选的
'''
# 声明外部变量
nonlocal timeout, th
th.setDaemon(True) # 设置为守护线程,会在它的父线程结束时自动结束
th.start()
th.join(secs)
timeout = th.is_alive()
guard = threading.Thread(target=daemon_thread)
guard.start()
guard.join()
return timeout
# 循环输出s,t次,每0.6秒一次
def say(s,t):
for i in range(t):
print(s)
time.sleep(0.6)
if __name__ == "__main__":
# 输出字符串3次,每次间隔0.6s,2s后超时,所以此处应该不会超时结束
timeout = start_thread_with_timeout(target=say, args=("Say 3 times", 3), secs=2)
print("Timeout? ", timeout)
# 输出字符串999次,每次间隔0.6s,2s后超时,所以此处应该不会超时结束
timeout = start_thread_with_timeout(target=say, args=("Say 999 times", 999), secs=2)
print("Timeout? ", timeout)
程序输出为:
Say 3 times
Say 3 times
Say 3 times
Timeout? False
Say 999 times
Say 999 times
Say 999 times
Say 999 times
Timeout? True
清楚原理后也可以用来实现kill的功能,例如可以让哨兵线程每隔一个时间片查询一次需要kill的flag,不过这样没有办法实时杀死线程,时间片设太大就延迟杀死,设太小会频繁占用CPU,解决方法可以通过信号量(semaphore)或者条件变量(condition)让哨兵线程陷入睡眠,外部调用kill唤醒哨兵线程直接结束就可以了。
(哨兵线程:守卫线程的父线程,可以看做是真正要执行的线程的哨兵,所以我称之为哨兵线程)
import threading
import time
class KillableThread:
def __init__(self, target, args=None):
self.th = threading.Thread(target=target, args=args)
self.kill_sema = threading.Semaphore(0)
self.start_sema = threading.Semaphore(0)
def daemon_thread(self):
self.th.setDaemon(True)
self.th.start()
self.start_sema.release()
self.kill_sema.acquire()
self.guard = threading.Thread(target=daemon_thread, args=(self,))
def start(self):
self.guard.start()
self.start_sema.acquire()
def join(self, secs=None):
self.th.join(secs)
if not self.th.is_alive():
self.kill_sema.release()
def is_alive(self):
return self.th.is_alive() and self.guard.is_alive()
def kill(self):
self.kill_sema.release()
while self.guard.is_alive():
pass
# 循环输出s,t次,每0.6秒一次
def say(s,t):
for i in range(t):
print(s)
time.sleep(0.6)
if __name__ == "__main__":
thread1 = KillableThread(target=say, args=("Say 3 times", 3))
thread1.start()
thread1.join()
print("Alive? ", thread1.is_alive())
thread2 = KillableThread(target=say, args=("Say 999 times", 999))
thread2.start()
thread2.join(2)
print("Alive? ", thread2.is_alive())
thread2.kill()
print("Still alive? ", thread2.is_alive())