在 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 中重新启动线程的主要内容,如果未能解决你的问题,请参考以下文章

启动多个线程并重新启动它们

在 C# 中出现异常时重新启动线程 [重复]

尝试重新启动线程时发生 ThreadStateException

无法重新启动线程[重复]

当旧线程死亡时重新启动函数

如果我们重新启动 Spring Boot 应用程序,正在运行的用户线程发生了啥?