Ожидание определенных тем в списке

1

У меня 3 потока: A, B и C.

Они находятся в списке тем и запускаются одновременно с

A.start()
B.start()
C.start()

Я не знаю, какая нить закончит сначала.

Мне нужно активировать действие для каждого потока, когда он заканчивается, например:

for t in threads:
    t.join()
    action()

Этот код неверен в этом случае, потому что: мне нужно дождаться завершения первого потока A до того, как B будет проверен, и дождитесь окончания проверки B для проверки C и т.д....

Как подождать каждый поток независимым образом, как если бы ".join()" было событием?

  • 1
    Как вы создаете темы?
  • 1
    Там нет необходимости join . Просто проверяйте is_alive для каждого из них в цикле, пока один из них не скажет, что это не так.
Показать ещё 1 комментарий
Теги:
python-3.x
multithreading
events

2 ответа

0
Лучший ответ

В вашем вопросе не содержится много деталей, поэтому я догадался и придумал следующее, чтобы продемонстрировать один подход:

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
  • 0
    Спасибо большое
  • 0
    Это плохая идея. Ваш основной поток пытается использовать 100% занятых процессором опросов потоков. Если они ничего не делают, это означает, что вы используете процессор на 100% вместо 1%. Если они должны делать что - либо, это означает , что вы украсть GIL от них. (Даже если они в основном - ввод-вывод с небольшим количеством процессора, наличие четырех потоков, борющихся за GIL вместо трех, может существенно изменить ситуацию.)
Показать ещё 1 комментарий
0

То, что вы ищете, - это способ подождать в группе потоков, и 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)
  • 0
    Спасибо большое
  • 0
    Мне кажется, что многие из предложенных вами циклов кода изменяют итерируемый объект, что может вызвать проблемы, как я уверен, вы знаете.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню