Threading.Thread: примеры (PYTHON)
threading.Thread(target: callable, args: tuple): Thread objectОсновы threading.Thread
Класс threading.Thread из одноименного модуля в Python представляет поток выполнения. Применяется для параллельного выполнения задач внутри одного процесса, что особенно полезно для операций ввода-вывода (I/O-bound), например, сетевых запросов или работы с файлами, где ожидание ответа блокирует выполнение. Это позволяет эффективно использовать время простоя центрального процессора.
Конструктор класса принимает несколько аргументов:
- group: всегда должен быть
None, оставлен для возможного расширения в будущем. - target: вызываемый объект (функция или метод), который будет выполняться в потоке после его запуска.
- name: имя потока (строка). По умолчанию генерируется уникальное имя вида «Thread-N».
- args: кортеж аргументов для передачи в целевую функцию (
target). По умолчанию - пустой кортеж(). - kwargs: словарь именованных аргументов для передачи в целевую функцию. По умолчанию - пустой словарь
{}. - daemon: булево значение. Если
True, поток становится демон-потоком. Главный поток завершит работу, не дожидаясь завершения таких потоков. По умолчанию наследуется от создающего потока.
Метод start() инициирует выполнение потока, вызывая метод run(), который, в свою очередь, исполняет целевую функцию. Метод join(timeout=None) блокирует вызывающий поток до завершения работы целевого потока или истечения таймаута. Класс не возвращает значений напрямую; результат работы целевой функции необходимо сохранять иным способом, например, через атрибуты экземпляра или внешние структуры данных.
Простые примеры использования
Поток с передачей позиционных аргументов:
import threading
import time
def task(name, delay):
time.sleep(delay)
print(f"Поток {name} завершил работу")
thread1 = threading.Thread(target=task, args=("A", 2))
thread1.start()
thread1.join()
print("Основной поток продолжает выполнение")Поток A завершил работу Основной поток продолжает выполнение
Создание потока-демона с именем:
def infinite_loop():
while True:
print("Фоновая задача")
time.sleep(1)
daemon_thread = threading.Thread(target=infinite_loop, name="DaemonExample", daemon=True)
daemon_thread.start()
time.sleep(2.5)
print("Основной поток завершается, не дожидаясь демона")Фоновая задача Фоновая задача Основной поток завершается, не дожидаясь демона
Наследование от класса Thread:
class CustomThread(threading.Thread):
def __init__(self, value):
super().__init__()
self.value = value
self.result = None
def run(self):
self.result = self.value * 2
custom = CustomThread(21)
custom.start()
custom.join()
print(f"Результат вычисления: {custom.result}")Результат вычисления: 42
Альтернативные подходы в Python
Для многозадачности в Python также доступны другие инструменты, выбор которых зависит от характера решаемой задачи.
multiprocessing.Process предназначен для создания отдельных процессов, каждый со своей памятью. Подходит для CPU-bound задач (интенсивные вычисления), так как обходит ограничение Global Interpreter Lock (GIL). Недостаток - более высокие накладные расходы на создание и межпроцессное взаимодействие.
concurrent.futures.ThreadPoolExecutor предоставляет высокоуровневый интерфейс для асинхронного выполнения задач с использованием пула потоков. Удобен для запуска множества однотипных задач с возможностью получения результатов через объекты Future. Часто предпочтительнее низкоуровневых потоков для типичных сценариев.
asyncio - библиотека для асинхронного программирования на основе цикла событий. Использует корутины и не блокирующий ввод-вывод. Эффективна для высоконагруженных сетевых приложений, но требует переписывания кода с использованием ключевых слов async/await.
Реализации в других языках
JavaScript (Node.js): модуль worker_threads для создания потоков. В отличие от Python, потоки в Node.js имеют изолированные контексты и общаются через обмен сообщениями.
const { Worker, isMainThread } = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
worker.on('message', (msg) => console.log(msg));
} else {
parentPort.postMessage('Привет из потока!');
}Привет из потока!
Java: класс Thread и интерфейс Runnable. Потоки в Java являются более низкоуровневыми и тесно интегрированы с виртуальной машиной.
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> System.out.println("Поток выполнен"));
thread.start();
try { thread.join(); } catch (InterruptedException e) {}
}
}Поток выполнен
Go: используются горутины (goroutines), которые представляют собой легковесные потоки, управляемые рантаймом языка. Для запуска функции в горутине применяется ключевое слово go.
package main
import "fmt"
func task() { fmt.Println("Горутина") }
func main() {
go task()
// Ожидание завершения (на практике используют sync.WaitGroup или каналы)
fmt.Scanln()
}Горутина
Kotlin: корутины, предоставляемые библиотекой kotlinx.coroutines, являются предпочтительным способом асинхронного программирования, хотя доступны и классические Java-потоки.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L)
println("Корутина")
}
println("Основной поток")
}Основной поток Корутина
Распространенные ошибки
Попытка повторного запуска одного и того же объекта потока приводит к исключению RuntimeError.
import threading
def work():
pass
th = threading.Thread(target=work)
th.start()
th.start() # Ошибка!RuntimeError: threads can only be started once
Отсутствие синхронизации при доступе к общим данным из нескольких потоков может вызывать состояние гонки (race condition) и приводить к недетерминированному поведению.
counter = 0
def increment():
global counter
for _ in range(100000):
counter += 1
threads = []
for i in range(5):
t = threading.Thread(target=increment)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Ожидалось 500000, получено {counter}")Ожидалось 500000, получено 343712 # Результат может отличаться
Запуск долгих операций в не-демонических потоках без вызова join() может привести к завершению программы до окончания работы потоков.
История изменений
В версии Python 3.10 в методах потоков, таких как join(), было улучшено описание исключений в документации. Существенных изменений в API класса threading.Thread в последних основных релизах не происходило. Аргумент daemon стал доступен в конструктоле, начиная с Python 3.3. Рекомендуется всегда обращаться к официальной документации для актуальной информации.
Расширенные сценарии
Использование блокировки (Lock) для синхронизации доступа к общему ресурсу:
import threading
lock = threading.Lock()
shared_list = []
def safe_append(item):
with lock:
shared_list.append(item)
threads = []
for i in range(10):
t = threading.Thread(target=safe_append, args=(i,))
t.start()
threads.append(t)
for t in threads:
t.join()
print(shared_list)[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Передача исключений из дочернего потока в основной с помощью очереди (Queue):
import queue
def worker(q):
try:
raise ValueError("Ошибка в потоке")
except Exception as e:
q.put(e)
exception_queue = queue.Queue()
th = threading.Thread(target=worker, args=(exception_queue,))
th.start()
th.join()
if not exception_queue.empty():
exc = exception_queue.get()
print(f"Перехвачено исключение: {exc}")Перехвачено исключение: Ошибка в потоке
Ограничение количества одновременно работающих потоков с помощью семафора (Semaphore):
sem = threading.Semaphore(3) # Не более 3 потоков одновременно
def limited_task(id):
with sem:
print(f"Поток {id} начал работу")
time.sleep(1)
print(f"Поток {id} завершил работу")
for i in range(10):
threading.Thread(target=limited_task, args=(i,)).start()
time.sleep(4)Поток 0 начал работу Поток 1 начал работу Поток 2 начал работу Поток 0 завершил работу Поток 3 начал работу ... # Вывод будет показывать группы по 3 потока
Поток с возвратом результата через пользовательский класс:
class ResultThread(threading.Thread):
def __init__(self, target, args=(), kwargs={}):
super().__init__()
self.target = target
self.args = args
self.kwargs = kwargs
self.result = None
def run(self):
self.result = self.target(*self.args, **self.kwargs)
def compute_power(x, y):
return x ** y
rt = ResultThread(target=compute_power, args=(2, 8))
rt.start()
rt.join()
print(f"2^8 = {rt.result}")2^8 = 256