在Python中重新启动一个线程

sky*_*oop 9 python multithreading copy python-multithreading

我正在尝试为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
Run Code Online (Sandbox Code Playgroud)

threadingPython本身的角度来看,这种行为是有道理的,但这使我的工作更加困难.所以我使用字典实现了一个解决方案来存储初始线程并将其复制到一个新对象并在必要时启动它.不幸的是,这也不起作用.这是一个基本的例子:

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)
Run Code Online (Sandbox Code Playgroud)

返回:

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__()
Run Code Online (Sandbox Code Playgroud)

显然,threading对象复制是不安全的...无论如何重新启动线程而不是重新创建整个对象?

aba*_*ert 21

  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')
}    
Run Code Online (Sandbox Code Playgroud)

问题解决了.


  1. 您无法重新启动线程.

大多数平台都无法这样做.

从概念上讲,它没有任何意义.当一个线程结束时,它的堆栈已经死了; 其父母已被标记或发出信号; 一旦它加入,它的资源就会被破坏(包括内核级资源,比如它的进程表条目).重新启动它的唯一方法是创建一整套新的东西.您可以通过创建新线程来完成此操作.

所以,就这样做吧.如果您真的不想在内部处理异常,只需存储构造参数并使用它们来启动新线程.

您甚至可以为您创建自己的子类:

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)
Run Code Online (Sandbox Code Playgroud)

现在很容易"复制"线程(使用你想要的语义):

if not a_thread.is_alive():
    a_thread = a_thread.clone()
Run Code Online (Sandbox Code Playgroud)
  1. 是的,threading.Thread复制对象不安全

你期望发生什么?最好的情况是,你会在同一个操作系统级别的线程对象周围找到一个不同的包装器,所以你要欺骗Python,而不是注意到你正在尝试做非法的,可能是段错误的事情,它试图阻止你这样做.

  • 您的意思是在调用“threading.Thread”时使用“target=threadwrap(a)”吗? (4认同)