← Все вопросы

Потоки в Python запускаются, но программа завершается раньше них — забыл join?

Задан 4 месяца назад470 просмотров2 ответа
6

Пишу скрипт, который параллельно скачивает несколько страниц. Создаю потоки через 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 ответа

11
✓ Принятый ответ — помог автору

Тебе не хватает 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 обязателен.

4

Добавлю подводный камень: daemon нужно выставить ДО start(), после запуска поменять флаг уже нельзя — словишь RuntimeError. И не путай: join() у daemon-потока всё равно работает и ждёт его, просто без join при выходе программы демон молча умрёт.

Если потоков много и хочется не возиться с ручным join, посмотри в сторону concurrent.futures.ThreadPoolExecutor — там контекстный менеджер сам дожидается всех задач на выходе из with.

Ваш ответ

Войдите, чтобы ответить на вопрос.
Поддержать проект