Дан фрагмент Python с потоками и разделяемой переменной: import threading; x=0; def inc(): global x; for _ in range(1000000): x+=1; t1=threading.Thread(target=inc); t2=threading.Thread(target=inc); t1.start(); t2.start(); t1.join(); t2.join(); — почему итоговый x не всегда равен 2000000 и как это исправить
Проблема — состояние гонки: операция x+=1x += 1x+=1 не атомарна (чтение, прибавление, запись выполняются отдельными шагами). При переключении потоков между этими шагами обновления теряются, поэтому после двух потоков результат не всегда 200000020000002000000 (при 100000010000001000000 итераций на поток). Простой пример интералирования: - поток A читает x=0x=0x=0; - поток B читает x=0x=0x=0; - A пишет x=1x=1x=1; - B пишет x=1x=1x=1; вместо ожидаемых 222. Исправления: 1) Защищать критическую секцию mutex’ом (threading.Lock): ```python import threading x = 0 lock = threading.Lock() def inc(): global x for _ in range(100000010000001000000): with lock: x += 1 t1 = threading.Thread(target=inc) t2 = threading.Thread(target=inc) t1.start(); t2.start() t1.join(); t2.join() # x будет равен 200000020000002000000
``` 2) Если нужна реальная параллельность на нескольких ядрах (обойти GIL), использовать multiprocessing с общим значением и блокировкой: ```python from multiprocessing import Process, Value, Lock x = Value('i', 0) lock = Lock() def inc(): for _ in range(100000010000001000000): with lock: x.value += 1 p1 = Process(target=inc) p2 = Process(target=inc) p1.start(); p2.start() p1.join(); p2.join() # x.value будет 200000020000002000000
``` 3) Можно использовать сторонние атомарные типы/расширения, если нужна высокая производительность без глобальной блокировки. Вывод: нужно убрать состояние гонки — защитить запись в общую переменную (lock/atomic/multiprocessing).
Простой пример интералирования:
- поток A читает x=0x=0x=0;
- поток B читает x=0x=0x=0;
- A пишет x=1x=1x=1;
- B пишет x=1x=1x=1;
вместо ожидаемых 222.
Исправления:
1) Защищать критическую секцию mutex’ом (threading.Lock):
```python
import threading
x = 0
lock = threading.Lock()
def inc():
global x
for _ in range(100000010000001000000):
with lock:
x += 1
t1 = threading.Thread(target=inc)
t2 = threading.Thread(target=inc)
t1.start(); t2.start()
t1.join(); t2.join()
# x будет равен 200000020000002000000 ```
2) Если нужна реальная параллельность на нескольких ядрах (обойти GIL), использовать multiprocessing с общим значением и блокировкой:
```python
from multiprocessing import Process, Value, Lock
x = Value('i', 0)
lock = Lock()
def inc():
for _ in range(100000010000001000000):
with lock:
x.value += 1
p1 = Process(target=inc)
p2 = Process(target=inc)
p1.start(); p2.start()
p1.join(); p2.join()
# x.value будет 200000020000002000000 ```
3) Можно использовать сторонние атомарные типы/расширения, если нужна высокая производительность без глобальной блокировки.
Вывод: нужно убрать состояние гонки — защитить запись в общую переменную (lock/atomic/multiprocessing).