У меня 3 потока: A, B и C.
Они находятся в списке тем и запускаются одновременно с
A.start()
B.start()
C.start()
Я не знаю, какая нить закончит сначала.
Мне нужно активировать действие для каждого потока, когда он заканчивается, например:
for t in threads:
t.join()
action()
Этот код неверен в этом случае, потому что: мне нужно дождаться завершения первого потока A до того, как B будет проверен, и дождитесь окончания проверки B для проверки C и т.д....
Как подождать каждый поток независимым образом, как если бы ".join()" было событием?
В вашем вопросе не содержится много деталей, поэтому я догадался и придумал следующее, чтобы продемонстрировать один подход:
from random import random
from threading import Thread
from time import sleep
def callback(name):
print('thread {} finished'.format(name))
A = Thread(target=lambda: sleep(random()))
B = Thread(target=lambda: sleep(random()))
C = Thread(target=lambda: sleep(random()))
A.start()
B.start()
C.start()
threads = {'A': A, 'B': B, 'C': C}
while threads:
for name, thread in sorted(threads.items()):
if not thread.is_alive():
callback(name)
threads.pop(name)
break
print('done')
Типичный выход:
thread C finished
thread A finished
thread B finished
done
То, что вы ищете, - это способ подождать в группе потоков, и API Python этого не предоставляет. Итак, вы должны имитировать его.
Самый простой, но наименее эффективный способ - просто заняться опросом потоков:
while threads:
for thread in threads:
thread.join(timeout=0.1)
if not thread.is_alive():
action(thread)
threads.remove(thread)
break
time.sleep(0.1)
Лучшим решением является использование объекта синхронизации, который все потоки могут сигнализировать прямо перед их выходом. Вы можете сделать это с помощью простого Event
:
while threads:
evt.wait()
evt.clear()
for thread in threads:
if not thread.is_alive():
thread.join()
action(thread)
threads.remove(thread)
break
Тем не менее, это имеет такое же потенциальное состояние гонки, как и любое событие самовосстановления: вы можете пропустить триггеры. В некоторых случаях вы можете работать через все детали и определять, что гонка не может нанести никакого вреда. Если нет, вам нужно использовать нечто более сложное, например Condition
.
Или вы можете просто перейти на более высокий уровень и использовать Queue
чтобы потоки могли просто q.put(self)
перед выходом. Тогда основной поток легко получить право:
while threads:
thread = q.get()
thread.join()
action(thread)
threads.remove(thread)
Но стоит подумать, действительно ли вам нужны 3 функции потока или три функции задачи, которые вы можете запускать в пуле или исполнителе или, возможно, даже одну задачу с тремя разными аргументами.
Например, трудно упростить эту задачу для одной функции с тремя наборами аргументов:
with multiprocessing.dummy.Pool(3) as pool:
for result in pool.imap_unordered(func, [arg1, arg2, arg3]):
action(result)
Или для трех функций:
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as ex:
fs = [ex.submit(func) for func in funcs]
for f in concurrent.futures.as_completed(fs):
action(f.result())
Или вы даже можете просто направить action
на фьючерсы:
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as ex:
for func in funcs:
ex.submit(func).add_done_callback(action)
join
. Просто проверяйтеis_alive
для каждого из них в цикле, пока один из них не скажет, что это не так.