← Назад к вопросам
Общая ли память у потоков в Python
2.0 Middle🔥 101 комментариев
#Асинхронность и многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Общая ли память у потоков в Python
Ответ: ДА, потоки в Python имеют общую память
Потоки (threads) в одном процессе разделяют одно адресное пространство памяти. Это означает, что все потоки имеют доступ к одним и тем же переменным, объектам и структурам данных.
import threading
import time
# Общая переменная для всех потоков
shared_counter = 0
def increment_counter():
global shared_counter
# Оба потока работают с одной переменной!
for _ in range(1000000):
shared_counter += 1
# Создание двух потоков
thread1 = threading.Thread(target=increment_counter)
thread2 = threading.Thread(target=increment_counter)
start_time = time.time()
thread1.start()
thread2.start()
thread1.join() # Ждём завершения thread1
thread2.join() # Ждём завершения thread2
print(f"Значение счётчика: {shared_counter}")
print(f"Время выполнения: {time.time() - start_time:.2f} сек")
# Ожидали бы 2000000, но может быть меньше из-за race condition!
Визуализация: Потоки vs Процессы
┌─────────────────────────────────────┐
│ Один процесс Python │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Thread 1 │ │ Thread 2 │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ ┌─────────────────────────────────┐ │
│ │ ОБЩАЯ память для обоих потоков│ │
│ │ │ │
│ │ shared_counter = 0 │ │ ← Оба потока видят это!
│ │ shared_list = [1, 2, 3] │ │ ← Оба потока видят это!
│ │ shared_dict = {'key': 'value'} │ │ ← Оба потока видят это!
│ │ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘
В отличие от процессов:
┌─────────────────────┐ ┌─────────────────────┐
│ Процесс 1 │ │ Процесс 2 │
│ │ │ │
│ counter = 0 │ │ counter = 0 │ ← РАЗНЫЕ значения!
│ ОТДЕЛЬНАЯ память │ │ ОТДЕЛЬНАЯ память │
└─────────────────────┘ └─────────────────────┘
Проблема: Race Condition
Так как потоки имеют общую память, возникает проблема race condition:
import threading
shared_value = 0
def unsafe_increment():
global shared_value
# Эта операция НЕ атомарная!
# Она разбивается на 3 шага:
# 1. Загрузить shared_value в регистр
# 2. Увеличить на 1
# 3. Записать обратно в shared_value
shared_value += 1
threads = []
for i in range(10):
t = threading.Thread(target=unsafe_increment)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Результат: {shared_value}") # Ожидали 10, но может быть 5, 7, 9...
# Потому что потоки перезаписывают друг другу значения
Временная последовательность конфликта:
Время | Thread 1 | Thread 2 | shared_value
-------|----------------------|----------------------|----------
1 | Загрузить (0) | - | 0
2 | Увеличить (0->1) | - | 0
3 | - | Загрузить (0) | 0
4 | Записать (1) | - | 1
5 | - | Увеличить (0->1) | 1
6 | - | Записать (1) | 1 ← Потеря обновления!
Решение 1: Lock (Блокировка)
import threading
shared_value = 0
lock = threading.Lock() # Создаём блокировку
def safe_increment():
global shared_value
with lock: # Критическая секция
# Только один поток может быть здесь одновременно
shared_value += 1
threads = []
for i in range(10):
t = threading.Thread(target=safe_increment)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Результат: {shared_value}") # Всегда 10
Решение 2: Queue (очередь)
import threading
from queue import Queue
# Очередь для безопасного обмена данными между потоками
result_queue = Queue()
def worker(worker_id):
# Поток может безопасно положить данные в очередь
result = f"Результат {worker_id}"
result_queue.put(result)
threads = []
for i in range(5):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
# Получаем результаты безопасно
while not result_queue.empty():
print(result_queue.get())
Пример: Общая память в действии
import threading
import time
# Общий список для всех потоков
shared_list = []
def producer():
"""Добавляет элементы в общий список"""
for i in range(5):
shared_list.append(f"item_{i}")
print(f"Producer добавил: item_{i}")
time.sleep(0.1)
def consumer():
"""Читает элементы из общего списка"""
time.sleep(0.2) # Ждём, пока producer начнёт
while len(shared_list) < 5:
if shared_list:
item = shared_list.pop(0)
print(f"Consumer прочитал: {item}")
time.sleep(0.15)
t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"Финальный список: {shared_list}")
Потоки vs Процессы: сравнение памяти
import threading
import multiprocessing
# ПОТОКИ — общая память
print("=== ПОТОКИ ===")
shared_data = {'count': 0}
def thread_worker():
# Прямо модифицирует общий словарь
shared_data['count'] += 1
threads = [threading.Thread(target=thread_worker) for _ in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"Финальное значение: {shared_data['count']}") # 5 (но может быть меньше)
# ПРОЦЕССЫ — отдельная память
print("\n=== ПРОЦЕССЫ ===")
def process_worker(shared_dict, key):
# Каждый процесс имеет копию данных
shared_dict[key] = 1
if __name__ == '__main__':
with multiprocessing.Manager() as manager:
shared_dict = manager.dict()
processes = [
multiprocessing.Process(target=process_worker, args=(shared_dict, i))
for i in range(5)
]
for p in processes:
p.start()
for p in processes:
p.join()
print(f"Финальное значение: {len(shared_dict)}") # 5
GIL (Global Interpreter Lock)
Важно! В CPython (стандартной реализации Python) существует GIL — глобальная блокировка интерпретатора.
import threading
import time
def cpu_bound_task():
"""CPU-bound операция"""
count = 0
for i in range(100000000):
count += 1
return count
# Последовательное выполнение (один поток)
start = time.time()
cpu_bound_task()
cpu_bound_task()
sequential_time = time.time() - start
print(f"Последовательное время: {sequential_time:.2f} сек")
# Потоки (два потока)
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()
threaded_time = time.time() - start
print(f"Потоки время: {threaded_time:.2f} сек")
# ⚠️ Времена примерно одинаковые! GIL не позволяет истинный параллелизм
GIL не проблема для I/O-bound операций:
import threading
import time
import requests
def fetch_url(url):
"""I/O-bound операция"""
response = requests.get(url)
return len(response.text)
urls = ['https://httpbin.org/delay/1'] * 5
# Последовательно
start = time.time()
for url in urls:
fetch_url(url)
sequential_time = time.time() - start
print(f"Последовательное время: {sequential_time:.2f} сек") # ~5 сек
# С потоками
start = time.time()
threads = [threading.Thread(target=fetch_url, args=(url,)) for url in urls]
for t in threads:
t.start()
for t in threads:
t.join()
threaded_time = time.time() - start
print(f"Потоки время: {threaded_time:.2f} сек") # ~1 сек (параллелизм!)
Когда использовать потоки
Потоки хороши для:
- I/O-bound операции (сеть, файлы, БД)
- Простые задачи параллелизма
- Низкие накладные расходы
Потоки плохие для:
- CPU-bound операции (вычисления)
- Нужен истинный параллелизм → используй multiprocessing
Вывод
- ДА, потоки в Python имеют общую память
- Это создаёт проблемы race condition — нужны синхронизационные примитивы (Lock, Queue)
- GIL в CPython не позволяет истинный параллелизм для CPU-bound задач
- Потоки отлично работают для I/O-bound операций (сеть, файлы)
- Для CPU-bound операций используй multiprocessing или asyncio, а не threading