Что такое многопоточность (multithreading)?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Многопоточность (Multithreading) в Python
Определение
Многопоточность — это способ организации выполнения кода, при котором несколько потоков (threads) выполняются в одном процессе, разделяя одно адресное пространство памяти. Это позволяет выполнять несколько операций параллельно.
Основные концепции
Что такое поток?
Поток — это легковесный объект внутри процесса, который может выполнять код независимо от других потоков.
import threading
import time
def worker(name):
for i in range(3):
print(f"{name}: работаю {i+1}")
time.sleep(1)
# Создание и запуск потока
thread = threading.Thread(target=worker, args=("Поток-1",))
thread.start() # Запуск потока
thread.join() # Ожидание завершения
Проблема GIL (Global Interpreter Lock)
Это критически важный концепт для понимания многопоточности в Python!
Что это?
GIL — это мьютекс, который позволяет только одному потоку одновременно выполнять байт-код Python. Это означает, что даже на многоядерной системе только один поток может выполнять код Python в данный момент.
import threading
import time
def cpu_bound_task():
total = 0
for i in range(100_000_000):
total += i
return total
# Без многопоточности (последовательно)
start = time.time()
cpu_bound_task()
cpu_bound_task()
print(f"Последовательно: {time.time() - start:.2f}s") # 7 секунд
# С многопоточностью (GIL препятствует параллелизму)
start = time.time()
t1 = threading.Thread(target=cpu_bound_task)
t2 = threading.Thread(target=cpu_bound_task)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"Многопоточность: {time.time() - start:.2f}s") # ВСЁ ЕЩЕ 7 секунд!
Почему это плохо?
ГIL делает невозможным истинный параллелизм для CPU-bound операций, так что многопоточность в Python НЕ ускоряет вычисления.
Когда многопоточность полезна
Многопоточность отлично работает для I/O-bound операций (сетевые запросы, чтение файлов, запросы БД):
import threading
import requests
import time
def fetch_url(url):
response = requests.get(url)
print(f"Загрузил {url}: {len(response.content)} байт")
return response
urls = [
"https://example.com",
"https://google.com",
"https://github.com",
]
# Без многопоточности
start = time.time()
for url in urls:
fetch_url(url)
print(f"Последовательно: {time.time() - start:.2f}s") # 9 секунд
# С многопоточностью (НАМНОГО быстрее!)
start = time.time()
threads = []
for url in urls:
t = threading.Thread(target=fetch_url, args=(url,))
t.start()
threads.append(t)
for t in threads:
t.join()
print(f"Многопоточность: {time.time() - start:.2f}s") # 3 секунды
Время ожидания I/O операций не блокирует другие потоки, поэтому они могут продолжать работу.
Thread Pool (ThreadPoolExecutor)
Для управления большим количеством потоков лучше использовать пул потоков:
from concurrent.futures import ThreadPoolExecutor
import requests
urls = ["https://example.com"] * 100
# Пул из 5 потоков
with ThreadPoolExecutor(max_workers=5) as executor:
results = executor.map(requests.get, urls)
print(f"Загрузил {len(list(results))} страниц")
Преимущества:
- Автоматическое управление жизненным циклом потоков
- Ограничение количества активных потоков
- Простой API для обработки результатов
Синхронизация между потоками
Lock (Мьютекс)
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
with lock: # Только один поток может выполнять код внутри
temp = counter
temp += 1
counter = temp
threads = [threading.Thread(target=increment) for _ in range(100)]
for t in threads:
t.start()
for t in threads:
t.join()
print(counter) # 100 (без lock была бы ошибка)
Event (Сигнал)
import threading
import time
event = threading.Event()
def waiter():
print("Жду сигнал...")
event.wait() # Блокирует, пока Event не будет set
print("Сигнал получен!")
def signaler():
time.sleep(2)
print("Отправляю сигнал")
event.set() # Пробуждает все потоки, ждущие event
t1 = threading.Thread(target=waiter)
t2 = threading.Thread(target=signaler)
t1.start()
t2.start()
t1.join()
t2.join()
Semaphore (Семафор)
import threading
import time
sem = threading.Semaphore(2) # Максимум 2 потока одновременно
def limited_resource():
with sem:
print(f"{threading.current_thread().name} использует ресурс")
time.sleep(1)
threads = [threading.Thread(target=limited_resource) for _ in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()
Альтернативы многопоточности
Asyncio (асинхронность)
Для I/O-bound операций asyncio часто лучше, чем многопоточность:
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
urls = ["https://example.com"] * 10
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
results = await asyncio.gather(*tasks)
print(f"Загрузил {len(results)} страниц")
asyncio.run(main())
Multiprocessing
Для CPU-bound операций используй процессы, а не потоки:
from multiprocessing import Process
import time
def cpu_bound():
total = 0
for i in range(100_000_000):
total += i
return total
if __name__ == "__main__":
start = time.time()
p1 = Process(target=cpu_bound)
p2 = Process(target=cpu_bound)
p1.start()
p2.start()
p1.join()
p2.join()
print(f"Два процесса: {time.time() - start:.2f}s") # 3.5 секунды!
Когда использовать
- Многопоточность: I/O-bound операции (HTTP, БД, файлы)
- Asyncio: I/O-bound с большим количеством операций
- Multiprocessing: CPU-bound операции
Проблемы многопоточности
- Race conditions (состояние гонки)
- Deadlocks (взаимная блокировка)
- Сложность отладки
- GIL ограничивает параллелизм
Вывод
Многопоточность в Python — мощный инструмент для асинхронных операций ввода-вывода, но имеет ограничения из-за GIL для CPU-bound задач. Для больших систем рассмотри asyncio или multiprocessing в зависимости от типа нагрузки.