在 Python 中重新启动线程

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29692250/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 04:53:43  来源:igfitidea点击:

Restarting a thread in Python

pythonmultithreadingcopypython-multithreading

提问by skycoop

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.

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

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:\Python34\lib\threading.py", line 847, in start
    raise RuntimeError("threads can only be started once")
RuntimeError: threads can only be started once

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:

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)

which returns:

返回:

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__()

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

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

回答by CoMartel

As "figs" said, you should rather handle the exceptions inside the Thread than trying to restart it. see the exception documentation here : https://docs.python.org/2/tutorial/errors.html

正如“无花果”所说,您应该处理线程内部的异常而不是尝试重新启动它。请参阅此处的异常文档:https: //docs.python.org/2/tutorial/errors.html

It is much more simple and pythonic to do so.

这样做更简单和pythonic。

回答by abarnert

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

Problem solved.

问题解决了。



You cannot restart a thread.

您无法重新启动线程。

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(*args, **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()


Yes, threading.Threadobjects are not safe to copy

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

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.

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