在 Python 中重新启动线程
Posted
技术标签:
【中文标题】在 Python 中重新启动线程【英文标题】:Restarting a thread in Python 【发布时间】:2015-06-23 21:28:03 【问题描述】:我正在尝试为 Python 3.4 中的一个项目制作线程飞行软件,其中我需要线程重新启动自身,以防在传感器读取期间发生 I/O 错误或其他类似的侥幸崩溃。因此,我正在制作一个看门狗来检查线程是否已经死亡并重新启动它们。
起初我试图检查线程是否不再活动并重新启动它,这样做:
>>> if not a_thread.isAlive():
... a_thread.start()
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "c:\Python34\lib\threading.py", line 847, in start
raise RuntimeError("threads can only be started once")
RuntimeError: threads can only be started once
从threading
和 Python 本身的角度来看,这种行为是有意义的,但会使我的工作更加困难。所以我实现了一个解决方案,使用字典来存储初始线程并将其复制到新对象并在必要时启动它。不幸的是,这也不起作用。
这是一个基本示例:
import threading
import logging
import queue
import time
from copy import copy, deepcopy
def a():
print("I'm thread a")
def b():
print("I'm thread b")
# Create thread objects
thread_dict =
'a': threading.Thread(target=a, name='a'),
'b': threading.Thread(target=b, name='b')
threads = [copy(t) for t in thread_dict.values()]
for t in threads:
t.start()
for i in range(len(threads)):
if not threads[i].isAlive():
temp = thread_dict[threads[i].name]
threads[i] = deepcopy(temp)
threads[i].start()
thread(i).join(5)
返回:
I'm thread a
I'm thread b
Traceback (most recent call last):
File "main_test.py", line 25, in <module>
threads[i] = deepcopy(temp)
File "c:\Python34\lib\copy.py", line 182, in deepcopy
y = _reconstruct(x, rv, 1, memo)
... (there's about 20 lines of traceback within copy)
File "c:\Python34\lib\copyreg.py", line 88, in __newobj__
return cls.__new__(cls, *args)
TypeError: object.__new__(_thread.lock) is not safe, use _thread.lock.__new__()
显然threading
对象不能安全复制...是否有重新启动线程而不是重新创建整个对象?
【问题讨论】:
你不能只处理线程内的崩溃而不需要重新启动吗? 不要重启它只是在上面写一个包装器***.com/a/61669925/13494084 【参考方案1】:没有理由让你的线程死掉。
如果它们真的崩溃了,你的整个程序就会崩溃。
如果它们只是引发异常,您可以捕获异常。
如果他们正常返回,你不能这样做。
您甚至可以简单地包装一个线程函数以在异常或返回时重新启动:
def threadwrap(threadfunc):
def wrapper():
while True:
try:
threadfunc()
except BaseException as e:
print('!r; restarting thread'.format(e))
else:
print('exited normally, bad thread; restarting')
return wrapper
thread_dict =
'a': threading.Thread(target=wrapper(a), name='a'),
'b': threading.Thread(target=wrapper(b), name='b')
问题解决了。
您不能重新启动线程。
大多数平台都没有办法这样做。
从概念上讲,它没有任何意义。当一个线程完成时,它的堆栈就死了;它的父级被标记或发出信号;一旦加入,它的资源就会被破坏(包括内核级资源,如它的进程表条目)。重新启动它的唯一方法是创建一套全新的一切。您已经可以通过创建一个新线程来做到这一点。
所以,就去做吧。如果您真的不想在内部处理异常,只需存储构造参数并使用它们来启动新线程。
您甚至可以为您创建自己的子类:
class RestartableThread(threading.Thread):
def __init__(self, *args, **kwargs):
self._args, self._kwargs = args, kwargs
super().__init__(*args, **kwargs)
def clone(self):
return RestartableThread(*self._args, **self._kwargs)
现在很容易“复制”线程(使用您想要的语义):
if not a_thread.is_alive():
a_thread = a_thread.clone()
是的,threading.Thread
对象不能安全复制
您希望发生什么?充其量,你会得到一个围绕同一个操作系统级线程对象的不同包装器,所以你会欺骗 Python,使其没有注意到你正在尝试做它试图阻止你做的非法的、可能导致段错误的事情正在做。
【讨论】:
您的意思是在对threading.Thread
的调用中使用target=threadwrap(a)
吗?【参考方案2】:
正如“figs”所说,您应该处理线程内部的异常而不是尝试重新启动它。 请参阅此处的异常文档: https://docs.python.org/2/tutorial/errors.html
这样做更简单,也更 Pythonic。
【讨论】:
【参考方案3】:这里是如何完全重新启动线程的示例。 也许这不是一个好的解决方案,但它非常适合我的目的。
#!/usr/bin/python3
import threading
from time import sleep
def thread1(thread_id, number):
thr = threading.currentThread()
print("[thread id:%d] start function" % (thread_id))
while True:
if getattr(thr, "need_stop", False):
print("[thread id:%d] Thread was stopped by external signal number"%(thread_id))
exit(0)
print("[thread id:%d] Number: %d<- "%(thread_id, number))
sleep(1)
def main():
thread_1 = []
thread_2 = []
for i in range(10):
sleep(0.5)
if i in [0,1,4,6]:
if len(thread_2) != 0:
for thr2 in thread_2:
thr2.need_stop = True
thr2.join()
thread_2.remove(thr2)
if 'thr_1' not in locals():
thr_1 = threading.Thread(target=thread1, args=([1, i]))
if thr_1.is_alive() is False:
try:
thr_1.start()
thread_1.append(thr_1)
except Exception as e:
del thr_1
thr_1 = threading.Thread(target=thread1, args=([1, i]))
thr_1.start()
thread_1.append(thr_1)
else:
if len(thread_1) != 0:
for thr1 in thread_1:
thr1.need_stop = True
thr1.join()
thread_1.remove(thr1)
if 'thr_2' not in locals():
thr_2 = threading.Thread(target=thread1, args=([2, i]))
if thr_2.is_alive() is False:
try:
thr_2.start()
thread_2.append(thr_2)
except Exception as e:
del thr_2
thr_2 = threading.Thread(target=thread1, args=([2, i]))
thr_2.start()
thread_2.append(thr_2)
# finish all threads
if len(thread_2) != 0:
for thr2 in thread_2:
thr2.need_stop = True
thr2.join()
thread_2.remove(thr2)
if len(thread_1) != 0:
for thr1 in thread_1:
thr1.need_stop = True
thr1.join()
thread_1.remove(thr1)
if __name__ == '__main__':
main()
代码的要点是,如果线程已经在运行,则不要触摸它。在输出中,您可以看到没有数字 1,3,因为线程已经在运行。 如果
输出:
$ python3 test_thread.py
[thread id:1] start function
[thread id:1] Number: 0<-
[thread id:1] Number: 0<-
[thread id:1] Thread was stopped by external signal number
[thread id:2] start function
[thread id:2] Number: 2<-
[thread id:2] Number: 2<-
[thread id:2] Thread was stopped by external signal number
[thread id:1] start function
[thread id:1] Number: 4<-
[thread id:1] Thread was stopped by external signal number
[thread id:2] start function
[thread id:2] Number: 5<-
[thread id:2] Thread was stopped by external signal number
[thread id:1] start function
[thread id:1] Number: 6<-
[thread id:1] Thread was stopped by external signal number
[thread id:2] start function
[thread id:2] Number: 7<-
[thread id:2] Number: 7<-
[thread id:2] Thread was stopped by external signal number
【讨论】:
以上是关于在 Python 中重新启动线程的主要内容,如果未能解决你的问题,请参考以下文章