Restarting a thread in Python
Question: 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:\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 `threading`and 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)
    

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 `threading` objects are not safe to copy... Is there anyway to
restart threads short of recreating the entire object?


Answer:   1. 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.

* * *

  2. 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()
    

* * *

  3. 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.

