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

Какая может возникнуть проблема при одновременной работы нескольких потоков?

1.8 Middle🔥 241 комментариев
#Python Core#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Проблемы многопоточности в Python

Одновременная работа потоков создаёт серьёзные проблемы с состоянием данных и синхронизацией. Рассмотрю основные категории.

1. Race Condition (Состояние гонки)

Несколько потоков обращаются к одному ресурсу одновременно, вызывая неопределённый результат:

import threading
import time

counter = 0

def increment():
    global counter
    for _ in range(100000):
        # Проблема: три операции, но не атомарные!
        temp = counter      # 1. Чтение
        temp += 1           # 2. Увеличение
        counter = temp      # 3. Запись

threads = [threading.Thread(target=increment) for _ in range(10)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(f\"Ожидаемо: 1000000, Получено: {counter}\")  # Обычно < 1000000!

Решение — использовать Lock:

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100000):
        with lock:
            counter += 1  # Теперь безопасно

threads = [threading.Thread(target=increment) for _ in range(10)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(f\"Результат: {counter}\")  # Всегда 1000000

2. Deadlock (Взаимная блокировка)

Два потока ждут друг друга, создавая бесконечное ожидание:

import threading
import time

lock1 = threading.Lock()
lock2 = threading.Lock()

def thread1_func():
    with lock1:
        print(\"Thread 1 захватил lock1\")
        time.sleep(1)
        with lock2:  # Ждёт lock2, который захватил thread2
            print(\"Thread 1 захватил lock2\")

def thread2_func():
    with lock2:
        print(\"Thread 2 захватил lock2\")
        time.sleep(1)
        with lock1:  # Ждёт lock1, который захватил thread1
            print(\"Thread 2 захватил lock1\")

t1 = threading.Thread(target=thread1_func)
t2 = threading.Thread(target=thread2_func)

t1.start()
t2.start()

t1.join()  # Бесконечное ожидание!
t2.join()

Решение — жёсткий порядок захвата:

import threading

lock1 = threading.Lock()
lock2 = threading.Lock()

def thread1_func():
    with lock1:
        with lock2:  # Всегда lock1 первым
            pass

def thread2_func():
    with lock1:  # Тот же порядок!
        with lock2:
            pass

3. Data Corruption (Повреждение данных)

Одновременное изменение структуры данных повреждает её:

import threading

data = {}

def add_data(key, value):
    for i in range(1000):
        # Небезопасно при одновременном доступе
        data[key] = value + i

threads = [
    threading.Thread(target=add_data, args=(\"key1\", 100)),
    threading.Thread(target=add_data, args=(\"key2\", 200)),
]

for t in threads:
    t.start()
for t in threads:
    t.join()

print(data)  # Результаты непредсказуемы

Решение — потокобезопасные структуры:

import threading
from queue import Queue

data_queue = Queue()  # Потокобезопасна!

def add_data(key, value):
    for i in range(10):
        data_queue.put((key, value + i))

threads = [
    threading.Thread(target=add_data, args=(\"key1\", 100)),
    threading.Thread(target=add_data, args=(\"key2\", 200)),
]

for t in threads:
    t.start()
for t in threads:
    t.join()

while not data_queue.empty():
    print(data_queue.get())

4. Starvation (Голодание потока)

Один поток монополизирует ресурс, другие ждут:

import threading

lock = threading.Lock()

def greedy_thread():
    for _ in range(1000):
        with lock:
            # Держит lock долго
            for i in range(1000000):
                pass

def starving_thread():
    for _ in range(10):
        with lock:
            print(\"I finally got it!\")

t1 = threading.Thread(target=greedy_thread)
t2 = threading.Thread(target=starving_thread)

t1.start()
t2.start()

t1.join()
t2.join()  # Может ждать очень долго

5. GIL (Global Interpreter Lock)

Python GIL предотвращает истинный параллелизм для потоков:

import threading
import time

def cpu_bound_task():
    total = 0
    for i in range(100000000):
        total += i
    return total

# Однопоточный
start = time.time()
cpu_bound_task()
print(f\"Однопоточно: {time.time() - start:.2f}s\")

# Двухпоточный (медленнее!)
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\")  # Медленнее!

Решение для CPU-bound — использовать multiprocessing:

import multiprocessing
import time

def cpu_bound_task():
    total = 0
    for i in range(100000000):
        total += i
    return total

# Процессы имеют отдельные GIL
start = time.time()
with multiprocessing.Pool(2) as pool:
    pool.map(lambda x: cpu_bound_task(), [1, 2])
print(f\"Два процесса: {time.time() - start:.2f}s\")  # Быстро!

6. Memory Visibility (Видимость в памяти)

Один поток не видит изменения другого потока:

import threading
import time

flag = False

def setter():
    global flag
    time.sleep(1)
    flag = True  # Другой поток может не увидеть!
    print(\"Flag set to True\")

def getter():
    while not flag:
        pass  # Может зависнуть, хотя flag уже True
    print(\"Flag is True\")

t1 = threading.Thread(target=setter)
t2 = threading.Thread(target=getter)

t2.start()
t1.start()

t1.join()
t2.join()

Решение — использовать Event:

import threading
import time

event = threading.Event()

def setter():
    time.sleep(1)
    event.set()  # Гарантированно видимо
    print(\"Flag set\")

def getter():
    event.wait()  # Гарантированно увидит
    print(\"Flag is set\")

t1 = threading.Thread(target=setter)
t2 = threading.Thread(target=getter)

t2.start()
t1.start()

t1.join()
t2.join()

Практические рекомендации

  1. Для I/O задач — используй threading (сетевые запросы, файлы)
  2. Для CPU задач — используй multiprocessing
  3. Для асинхрона — используй asyncio (более безопасно)
  4. Минимизируй критические секции — держи lock как можно короче
  5. Тестируй с стрессом — race conditions проявляются редко
  6. Используй инструменты — ThreadSanitizer, Valgrind для поиска проблем
  7. Логируй конкурентность — помогает отладке

Многопоточность требует тщательного обдумывания и тестирования!

Какая может возникнуть проблема при одновременной работы нескольких потоков? | PrepBro