Таймауты потоков и потоки-демоны
На этом заключительном шаге вы узнаете о двух важных концепциях управления потоками: установке таймаутов и использовании потоков-демонов. Эти методы дают вам больше контроля над поведением потоков и их взаимодействием с основной программой.
Работа с таймаутами потоков
Как вы узнали на шаге 2, метод join()
принимает параметр таймаута. Это полезно, когда вы хотите дождаться завершения потока, но только до определенного момента.
Давайте создадим более практичный пример, в котором мы реализуем функцию, которая пытается получить данные с таймаутом:
-
Создайте новый файл с именем thread_with_timeout.py
в каталоге /home/labex/project
.
-
Добавьте следующий код:
import threading
import time
import random
def fetch_data(data_id):
"""Simulate fetching data that might take varying amounts of time."""
print(f"Fetching data #{data_id}...")
## Simulate different fetch times, occasionally very long
fetch_time = random.choices([1, 8], weights=[0.8, 0.2])[0]
time.sleep(fetch_time)
if fetch_time > 5: ## Simulate a slow fetch
print(f"Data #{data_id}: Fetch took too long!")
return None
else:
print(f"Data #{data_id}: Fetch completed in {fetch_time} seconds!")
return f"Data content for #{data_id}"
def fetch_with_timeout(data_id, timeout=3):
"""Fetch data with a timeout."""
result = [None] ## Using a list to store result from the thread
def target_func():
result[0] = fetch_data(data_id)
## Create and start the thread
thread = threading.Thread(target=target_func)
thread.start()
## Wait for the thread with a timeout
thread.join(timeout=timeout)
if thread.is_alive():
print(f"Data #{data_id}: Fetch timed out after {timeout} seconds!")
return "TIMEOUT"
else:
return result[0]
## Try to fetch several pieces of data
for i in range(1, 6):
print(f"\nAttempting to fetch data #{i}")
result = fetch_with_timeout(i, timeout=3)
if result == "TIMEOUT":
print(f"Main thread: Fetch for data #{i} timed out, moving on...")
elif result is None:
print(f"Main thread: Fetch for data #{i} completed but returned no data.")
else:
print(f"Main thread: Successfully fetched: {result}")
print("\nAll fetch attempts completed!")
- Сохраните файл и запустите его:
python3 /home/labex/project/thread_with_timeout.py
Вывод будет меняться, но должен выглядеть примерно так:
Attempting to fetch data #1
Fetching data #1...
Data #1: Fetch completed in 1 seconds!
Main thread: Successfully fetched: Data content for #1
Attempting to fetch data #2
Fetching data #2...
Data #2: Fetch completed in 1 seconds!
Main thread: Successfully fetched: Data content for #2
Attempting to fetch data #3
Fetching data #3...
Data #3: Fetch timed out after 3 seconds!
Main thread: Fetch for data #3 timed out, moving on...
Data #3: Fetch took too long!
Attempting to fetch data #4
Fetching data #4...
Data #4: Fetch completed in 1 seconds!
Main thread: Successfully fetched: Data content for #4
Attempting to fetch data #5
Fetching data #5...
Data #5: Fetch completed in 1 seconds!
Main thread: Successfully fetched: Data content for #5
All fetch attempts completed!
Этот пример демонстрирует:
- Функцию, которая пытается получить данные и может быть медленной
- Функцию-обертку, которая использует потоки с таймаутом
- Как корректно обрабатывать таймауты и продолжать другие операции
Понимание потоков-демонов
В Python потоки-демоны — это потоки, которые работают в фоновом режиме. Ключевое различие между потоками-демонами и недемонскими потоками заключается в том, что Python не будет ждать завершения потоков-демонов перед выходом. Это полезно для потоков, которые выполняют фоновые задачи, которые не должны мешать выходу из программы.
Давайте создадим пример, чтобы продемонстрировать потоки-демоны:
-
Создайте новый файл с именем daemon_threads.py
в каталоге /home/labex/project
.
-
Добавьте следующий код:
import threading
import time
def background_task(name, interval):
"""A task that runs in the background at regular intervals."""
count = 0
while True:
count += 1
print(f"{name}: Iteration {count} at {time.strftime('%H:%M:%S')}")
time.sleep(interval)
def main_task():
"""The main task that runs for a set amount of time."""
print("Main task: Starting...")
time.sleep(5)
print("Main task: Completed!")
## Create two background threads
print("Creating background monitoring threads...")
monitor1 = threading.Thread(target=background_task, args=("Monitor-1", 1), daemon=True)
monitor2 = threading.Thread(target=background_task, args=("Monitor-2", 2), daemon=True)
## Start the background threads
monitor1.start()
monitor2.start()
print("Background monitors started, now starting main task...")
## Run the main task
main_task()
print("Main task completed, program will exit without waiting for daemon threads.")
print("Daemon threads will be terminated when the program exits.")
- Сохраните файл и запустите его:
python3 /home/labex/project/daemon_threads.py
Вывод должен выглядеть примерно так:
Creating background monitoring threads...
Background monitors started, now starting main task...
Main task: Starting...
Monitor-1: Iteration 1 at 14:25:10
Monitor-2: Iteration 1 at 14:25:10
Monitor-1: Iteration 2 at 14:25:11
Monitor-1: Iteration 3 at 14:25:12
Monitor-2: Iteration 2 at 14:25:12
Monitor-1: Iteration 4 at 14:25:13
Monitor-1: Iteration 5 at 14:25:14
Monitor-2: Iteration 3 at 14:25:14
Main task: Completed!
Main task completed, program will exit without waiting for daemon threads.
Daemon threads will be terminated when the program exits.
В этом примере:
- Мы создали два потока-демона, которые работают непрерывно, выводя сообщения через регулярные промежутки времени.
- Мы установили
daemon=True
при создании потоков, что помечает их как потоки-демоны.
- Главный поток работает 5 секунд, а затем завершается.
- Когда главный поток завершается, программа завершается, и потоки-демоны также автоматически завершаются.
Недемонские потоки против потоков-демонов
Чтобы лучше понять разницу, давайте создадим еще один пример, который сравнивает потоки-демоны и недемонские потоки:
-
Создайте новый файл с именем daemon_comparison.py
в каталоге /home/labex/project
.
-
Добавьте следующий код:
import threading
import time
def task(name, seconds, daemon=False):
"""A task that runs for a specified amount of time."""
print(f"{name} starting {'(daemon)' if daemon else '(non-daemon)'}")
time.sleep(seconds)
print(f"{name} finished after {seconds} seconds")
## Create a non-daemon thread that runs for 8 seconds
non_daemon_thread = threading.Thread(
target=task,
args=("Non-daemon thread", 8, False),
daemon=False ## This is the default, so it's not actually needed
)
## Create a daemon thread that runs for 8 seconds
daemon_thread = threading.Thread(
target=task,
args=("Daemon thread", 8, True),
daemon=True
)
## Start both threads
non_daemon_thread.start()
daemon_thread.start()
## Let the main thread run for 3 seconds
print("Main thread will run for 3 seconds...")
time.sleep(3)
## Check which threads are still running
print("\nAfter 3 seconds:")
print(f"Daemon thread is alive: {daemon_thread.is_alive()}")
print(f"Non-daemon thread is alive: {non_daemon_thread.is_alive()}")
print("\nMain thread is finishing. Here's what will happen:")
print("1. The program will wait for all non-daemon threads to complete")
print("2. Daemon threads will be terminated when the program exits")
print("\nWaiting for non-daemon threads to finish...")
## We don't need to join the non-daemon thread, Python will wait for it
## But we'll explicitly join it for clarity
non_daemon_thread.join()
print("All non-daemon threads have finished, program will exit now.")
- Сохраните файл и запустите его:
python3 /home/labex/project/daemon_comparison.py
Вывод должен выглядеть так:
Non-daemon thread starting (non-daemon)
Daemon thread starting (daemon)
Main thread will run for 3 seconds...
After 3 seconds:
Daemon thread is alive: True
Non-daemon thread is alive: True
Main thread is finishing. Here's what will happen:
1. The program will wait for all non-daemon threads to complete
2. Daemon threads will be terminated when the program exits
Waiting for non-daemon threads to finish...
Non-daemon thread finished after 8 seconds
All non-daemon threads have finished, program will exit now.
Основные наблюдения:
- Оба потока запускаются и работают параллельно.
- Через 3 секунды оба потока все еще работают.
- Программа ждет завершения недемонского потока (через 8 секунд).
- Поток-демон все еще работает, когда программа завершается, но он завершается.
- Поток-демон никогда не успевает напечатать свое сообщение о завершении, потому что он завершается, когда программа завершается.
Когда использовать потоки-демоны
Потоки-демоны полезны для:
- Фоновых задач мониторинга
- Операций очистки
- Служб, которые должны работать в течение всей программы, но не мешать ее выходу
- Потоков таймера, которые запускают события через регулярные промежутки времени
Недемонские потоки подходят для:
- Критических операций, которые должны быть завершены
- Задач, которые не должны быть прерваны
- Операций, которые должны быть чисто завершены до выхода из программы
Понимание того, когда использовать каждый тип, является важной частью разработки надежных многопоточных приложений.