Короткий ответ
Модуль threading предоставляет класс Thread: поток создаётся вокруг callable-объекта, запускается методом start() и ожидается через join(). Из-за GIL в каждый момент исполняется один поток, поэтому многопоточность эффективна прежде всего для задач ввода-вывода.
Как это работает подробнее
Потоками управляет операционная система, но CPython через GIL ограничивает одновременное исполнение Python-байткода одним потоком.
- Глобальная блокировка. Для CPU-bound задач потоки не дают ускорения: остальные потоки ждут освобождения GIL.
- Ожидание ввода-вывода. Пока поток ждёт ответа от сети, диска или базы данных, GIL свободен, и другие потоки выполняют код.
- Синхронизация. При изменении общих данных возникают состояния гонки, поэтому используют блокировки Lock и семафоры Semaphore.
- Безопасная передача данных. Для обмена данными между потоками удобно использовать queue.Queue.
- Настоящий параллелизм. Чтобы задействовать несколько ядер процессора, применяют multiprocessing или библиотеки вроде joblib, запускающие вычисления в отдельных процессах.
Пример кода
Потоки с синхронизацией через Lock
import threading
lock = threading.Lock()
counter = 0
def worker():
global counter
for _ in range(1000):
with lock:
counter += 1
threads = [threading.Thread(target=worker) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
print(counter)Что использовать на практике
- I/O-bound задачи. Используй потоки для сетевых запросов, чтения файлов и обращений к базе данных.
- Общие данные. Защищай счётчики и коллекции блокировкой или передавай данные через queue.Queue.