Threading.Thread: примеры (PYTHON)

Параллельное выполнение задач с threading.Thread
Раздел: Многопоточность, Потоки
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) для синхронизации доступа к общему ресурсу:

Пример python
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):

Пример python
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):

Пример python
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 потока

Поток с возвратом результата через пользовательский класс:

Пример python
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

питон threading.Thread function comments

En
Threading.Thread Create thread