Рассмотрим ситуацию, в которой потоки читают показаний нескольких датчиков одновременно. В случае возникновения ошибки ввода/вывода во время чтения показаний одного из датчиков или другого подобного сбоя встает необходимость перезапускать упавший поток.
Первое, что приходит в голову это просто проверить, жив ли поток, и перезапустить его, если он умер:
# как то так
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, перезапуск...
# Читаем показания датчика А
# Читаем показания датчика А
# Читаем показания датчика Б
# Читаем показания датчика А
# ...