Потоки в Python запускаются, но программа завершается раньше них — забыл join?
Пишу скрипт, который параллельно скачивает несколько страниц. Создаю потоки через threading.Thread, вызываю start(), но иногда печать в конце происходит ДО того, как потоки доделают работу.
import threading
import time
def worker(n):
time.sleep(1)
print(f"поток {n} закончил")
threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]
for t in threads:
t.start()
print("всё готово") # печатается СРАЗУ, потоки ещё работают
Как заставить главную программу дождаться потоков? И что вообще делает daemon-поток, я где-то видел этот флаг.
2 ответа
Тебе не хватает join(). start() только запускает поток и сразу возвращает управление — главный поток летит дальше, не дожидаясь. Чтобы дождаться завершения, нужно для каждого потока вызвать join():
import threading
import time
def worker(n):
time.sleep(1)
print(f"поток {n} закончил")
threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]
for t in threads:
t.start() # запустили все три
for t in threads:
t.join() # ждём каждый
print("всё готово") # теперь печатается ПОСЛЕ потоков
Важный момент: сначала в одном цикле всё стартуем, потом в другом джойним. Если делать start(); join() внутри одного цикла — потоки пойдут последовательно, никакого параллелизма не будет, ты просто подождёшь каждый по очереди.
Про daemon: daemon-поток — это «фоновый» поток, который Python убивает автоматически при выходе из программы, не дожидаясь его. Обычные (non-daemon) потоки наоборот держат программу живой, пока не доработают. Daemon ставят так:
t = threading.Thread(target=worker, args=(0,), daemon=True)
Демоны удобны для бесконечных фоновых задач (логгер, heartbeat), которые не жалко прибить. Но если демон-поток что-то пишет в файл — его могут оборвать на полуслове, так что для важной работы join обязателен.
Добавлю подводный камень: daemon нужно выставить ДО start(), после запуска поменять флаг уже нельзя — словишь RuntimeError. И не путай: join() у daemon-потока всё равно работает и ждёт его, просто без join при выходе программы демон молча умрёт.
Если потоков много и хочется не возиться с ручным join, посмотри в сторону concurrent.futures.ThreadPoolExecutor — там контекстный менеджер сам дожидается всех задач на выходе из with.