Сообщить об ошибке.

Как перезапускать потоки в Python?

Как перезапускать потоки случае непредвиденных ошибок в Python?

Рассмотрим ситуацию, в которой потоки читают показаний нескольких датчиков одновременно. В случае возникновения ошибки ввода/вывода во время чтения показаний одного из датчиков или другого подобного сбоя встает необходимость перезапускать упавший поток.

Первое, что приходит в голову это просто проверить, жив ли поток, и перезапустить его, если он умер:

# как то так
if not current_thread.isAlive():
    current_thread.start()

Попробуем смоделировать ситуацию:

import threading, time

def sensor_a():
    while True:
        time.sleep(1)
        print("Читаем показания датчика А")

def sensor_b():
    i = 0
    while True:
        time.sleep(1)
        if i == 1:
            # имитируем ошибку при чтении датчика
            raise KeyboardInterrupt
        print("Читаем показания датчика Б")
        i += 1

# словарь с потоки для чтения счетчиков
# словарь необходим для их перезапуска
thread_dict = {
'SENSOR_A': threading.Thread(target=sensor_a, name='SENSOR_A'),
'SENSOR_B': threading.Thread(target=sensor_b, name='SENSOR_B')
}

threads = [t for t in thread_dict.values()]

# запускаем потоки
for t in threads:
    t.start()

# следим за потоками в реальном времени
while True:
    # проходимся по объектам потоков
    for thread in threading.enumerate():
        # если поток умер
        if not thread.isAlive():
            print(f'Поток, читающий {thread.name} умер')
            # получаем из словаря `thread_dict` 
            # поток по имени для его перезапуска
            restart = thread_dict[thread.name]
            # пытаемся перезапустить
            restart.start()

# Читаем показания датчика А
# Читаем показания датчика Б
# Читаем показания датчика А
# Exception in thread sensor_b:
# Traceback (most recent call last):
# ...
# KeyboardInterrupt
# Поток, читающий SENSOR_B умер
# Перезапуск потока SENSOR_B
# Traceback (most recent call last):
# ...
# RuntimeError: threads can only be started once
# Читаем показания датчика А
# Читаем показания датчика А
# ...

Вот и ответ на поставленный вопрос "RuntimeError: threads can only be started once" - потоки могут быть запущены только один раз! Другими словами потоки перезапускать НЕЛЬЗЯ.

В такой ситуации правильней и проще вообще не позволять умирать потокам, ведь если потоки на самом деле рушатся, то следом может упасть и вся программа. Если в функции потока, какой-то блок кода может вызывает исключение, то лучше подстраховаться и просто ловить это исключение, а в качестве результата выдать например None.

Если код программы уже написан и работает, а править очень долго, то можно поступить вообще тривиально, обернув функцию потока в другую функцию, которая будет ловить все исключения подряд.

Например:

import threading, time

def sensor_a():
    while True:
        time.sleep(1)
        print("Читаем показания датчика А")

def sensor_b():
    i = 0
    while True:
        time.sleep(1)
        if i == 1:
            # имитируем падение датчика
            raise KeyboardInterrupt
        print("Читаем показания датчика Б")
        i += 1

def threadwrap(threadfunc):
    def wrapper():
        while True:
            try:
                threadfunc()
            except BaseException as e:
                th_name = threading.current_thread().name
                print(f'Падение потока датчика {th_name}, перезапуск...')
    return wrapper

# словарь потоков
thread_dict = {
'SENSOR_A': threading.Thread(target=sensor_a, name='SENSOR_A'),
'SENSOR_B': threading.Thread(target=sensor_b, name='SENSOR_B')
}

# создаем потоки
threads = [t for t in thread_dict.values()]

# запускаем потоки
for t in threads:
    t.start()
    
# Читаем показания датчика Б
# Читаем показания датчика А
# Падение потока датчика SENSOR_B, перезапуск...
# Читаем показания датчика А
# Читаем показания датчика А
# Читаем показания датчика Б
# Читаем показания датчика А
# ...