我试图在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
个对象不能安全复制......无论如何重启线程都不能重建整个对象吗?
答案 0 :(得分:17)
如果他们真的崩溃了,你的整个程序都会崩溃。
如果他们只是提出异常,你可以抓住例外。
如果他们正常返回,你可以不这样做。
你甚至可以简单地包装一个线程函数来在异常时重启自己或者返回:
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(*args, **kwargs)
现在很容易“复制”线程(使用你想要的语义):
if not a_thread.is_alive():
a_thread = a_thread.clone()
threading.Thread
个对象无法安全复制您期望发生什么?最好的情况是,你会在同一个操作系统级别的线程对象周围找到一个不同的包装器,所以你要欺骗Python,而不是注意到你正试图做一些非法的,可能是段错误的东西,它试图阻止你做。
答案 1 :(得分:2)
As&#34; figs&#34;说,你应该更好地处理线程内部的异常,而不是尝试重新启动它。 请参阅此处的异常文档: https://docs.python.org/2/tutorial/errors.html
这样做更加简单和pythonic。
答案 2 :(得分:0)
这是如何完全重新启动线程的示例。 也许这不是一个好的解决方案,但它非常适合我的目的。
#!/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