69 lines
2.5 KiB
Python
69 lines
2.5 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""Демо: гонка данных — потерянные обновления счётчика.
|
||
|
||
Запуск из корня курса:
|
||
python lab05_sync/examples/01_race_demo.py
|
||
|
||
counter += 1 — три операции (чтение, инкремент, запись). Два потока читают
|
||
одно значение, оба пишут — одно увеличение теряется. Lock устраняет гонку.
|
||
"""
|
||
import os
|
||
import sys
|
||
import threading
|
||
import time
|
||
|
||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
||
|
||
N_THREADS = 8
|
||
N_INCR = 100_000
|
||
|
||
|
||
def worker_unsafe(counter):
|
||
for _ in range(N_INCR):
|
||
value = counter[0] # ЧТЕНИЕ
|
||
time.sleep(0) # усиливает переключения: GIL уступается
|
||
counter[0] = value + 1 # ЗАПИСЬ (могла потеряться)
|
||
|
||
|
||
def worker_safe(counter, lock):
|
||
for _ in range(N_INCR):
|
||
with lock: # критическая секция
|
||
value = counter[0]
|
||
counter[0] = value + 1
|
||
|
||
|
||
def main():
|
||
expected = N_THREADS * N_INCR
|
||
|
||
counter = [0]
|
||
threads = [threading.Thread(target=worker_unsafe, args=(counter,))
|
||
for _ in range(N_THREADS)]
|
||
t0 = time.perf_counter()
|
||
for t in threads:
|
||
t.start()
|
||
for t in threads:
|
||
t.join()
|
||
unsafe_time = time.perf_counter() - t0
|
||
print(f"без синхронизации: {counter[0]:>10,} из {expected:,} "
|
||
f"(потеряно {expected - counter[0]:,}) за {unsafe_time:.2f} c")
|
||
|
||
counter = [0]
|
||
lock = threading.Lock()
|
||
threads = [threading.Thread(target=worker_safe, args=(counter, lock))
|
||
for _ in range(N_THREADS)]
|
||
t0 = time.perf_counter()
|
||
for t in threads:
|
||
t.start()
|
||
for t in threads:
|
||
t.join()
|
||
safe_time = time.perf_counter() - t0
|
||
print(f"с Lock: {counter[0]:>10,} из {expected:,} "
|
||
f"(идеально) за {safe_time:.2f} c")
|
||
print(f"\nРазница времени: {(safe_time - unsafe_time) / unsafe_time * 100:+.0f}%")
|
||
print("(Lock может даже ускорить: без него потоки чаще конфликтуют)")
|
||
print("Запустите ещё раз — потерянные обновления будут другими:")
|
||
print("гонка недетерминирована, этим она и опасна.")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main() |