← Назад к вопросам

Общая ли память у потоков в 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
Общая ли память у потоков в Python | PrepBro