在 Python 中重新启动线程

问题描述

我正在尝试为 Python 3.4 中的项目制作线程飞行软件,其中我需要线程重新启动自身,以防在传感器读取期间发生 I/O 错误或其他类似的侥幸崩溃.因此,我正在制作一个看门狗来检查线程是否已经死亡并重新启动它们.

I'm trying to make threaded flight software for a project in Python 3.4, in which I need threads to restart themselves in case an I/O error occurs during a sensor read or another fluke crash like that. Therefore I am working on making a watchdog to check if threads have died and restarting them.

起初我试图检查线程是否不再活动并重新启动它,这样做:

At first I attempted to just check if the thread was no longer alive and restart it, which did this:

>>> if not a_thread.isAlive():
...     a_thread.start()
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "c:Python34lib	hreading.py", line 847, in start
    raise RuntimeError("threads can only be started once")
RuntimeError: threads can only be started once

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

This behaviour makes sense from the standpoint of threadingand Python itself, but makes my job harder. So I implemented a solution using a dictionary to store the initial thread and copy it to a new object and start it when necessary. Unfortunately this doesn't work either. Here's a basic example:

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:Python34libcopy.py", line 182, in deepcopy
    y = _reconstruct(x, rv, 1, memo)
  ... (there's about 20 lines of traceback within copy)
  File "c:Python34libcopyreg.py", line 88, in __newobj__
    return cls.__new__(cls, *args)
TypeError: object.__new__(_thread.lock) is not safe, use _thread.lock.__new__()

所以显然 threading 对象不能安全复制...有没有办法重新启动线程而不是重新创建整个对象?

So apparently threading objects are not safe to copy... Is there anyway to restart threads short of recreating the entire object?


解决方案

没有理由让你的线程死掉.

如果它们真的崩溃了,你的整个程序就会崩溃.

There's no reason to let your threads die.

If they're actually crashing, your whole program will crash.

如果它们只是引发异常,您可以捕获异常.

If they're just raising exceptions, you can just catch the exceptions.

如果他们正常返回,你不能这样做.

If they're returning normally, you can just not do that.

您甚至可以简单地包装一个线程函数以在异常或返回时重新启动自身:

You can even trivially wrap a thread function to restart itself on exception or return:

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')
}    

问题解决了.

大多数平台都没有办法这样做.

Most platforms have no way to do so.

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

And conceptually, it doesn't make any sense. When a thread finished, its stack is dead; its parent is flagged or signaled; once it's joined, its resources are destroyed (including kernel-level resources like its process table entry). The only way to restart it would be to create a whole new set of everything. Which you can already do by creating a new thread.

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

So, just do it. If you really don't want to handle the exceptions internally, just store the construction arguments and use them to start a new thread.

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

You can even create your own subclass that hangs onto them for you:

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)

现在很容易复制";线程(具有您想要的语义):

And now it's easy to "copy" the thread (with the semantics you wanted):

if not a_thread.is_alive():
    a_thread = a_thread.clone()


是的,threading.Thread 对象不能安全复制

您希望发生什么?充其量,你会得到一个围绕同一个操作系统级线程对象的不同包装器,所以你会欺骗 Python,使其没有注意到你正在尝试做非法的、可能导致段错误的事情,它试图阻止你做.


Yes, threading.Thread objects are not safe to copy

What would you expect to happen? At best, you'd get a different wrapper around the same OS-level thread object, so you'd fool Python into not noticing that you're trying to do the illegal, possibly segfault-inducing things it was trying to stop you from doing.

相关文章