Практические примеры и решения
Для более наглядного иллюстрации концепций ситуаций гонки и методов их предотвращения рассмотрим некоторые практические примеры и решения.
Пример 1: Увеличение счетчика
Предположим, у нас есть общий счетчик, который несколько потоков должны увеличивать. Без должной синхронизации может возникнуть ситуация гонки, что приведет к неправильному конечному значению счетчика.
import threading
## Общий счетчик
counter = 0
def increment_counter():
global counter
for _ in range(1000000):
counter += 1
## Создаем и запускаем потоки
threads = [threading.Thread(target=increment_counter) for _ in range(4)]
for thread in threads:
thread.start()
## Ждем завершения всех потоков
for thread in threads:
thread.join()
print(f"Конечное значение счетчика: {counter}")
Для предотвращения ситуации гонки мы можем использовать mutex, чтобы гарантировать, что одновременно к общему счетчику сможет обратиться только один поток:
import threading
## Общий счетчик
counter = 0
lock = threading.Lock()
def increment_counter():
global counter
for _ in range(1000000):
with lock:
counter += 1
## Создаем и запускаем потоки
threads = [threading.Thread(target=increment_counter) for _ in range(4)]
for thread in threads:
thread.start()
## Ждем завершения всех потоков
for thread in threads:
thread.join()
print(f"Конечное значение счетчика: {counter}")
Пример 2: Общий банковский счет
Рассмотрим ситуацию, когда несколько потоков обращаются к общему банковскому счету. Без должной синхронизации может возникнуть ситуация гонки, что приведет к неправильным балансам счета.
import threading
## Общий банковский счет
balance = 1000
def withdraw(amount):
global balance
if balance >= amount:
balance -= amount
print(f"Снято {amount}, новый баланс: {balance}")
else:
print("Недостаточно средств")
def deposit(amount):
global balance
balance += amount
print(f"Внесено {amount}, новый баланс: {balance}")
## Создаем и запускаем потоки
withdraw_thread = threading.Thread(target=withdraw, args=(500,))
deposit_thread = threading.Thread(target=deposit, args=(200,))
withdraw_thread.start()
deposit_thread.start()
## Ждем завершения всех потоков
withdraw_thread.join()
deposit_thread.join()
Для предотвращения ситуации гонки мы можем использовать mutex, чтобы гарантировать, что одновременно к общему банковскому счету сможет обратиться только один поток:
import threading
## Общий банковский счет
balance = 1000
lock = threading.Lock()
def withdraw(amount):
global balance
with lock:
if balance >= amount:
balance -= amount
print(f"Снято {amount}, новый баланс: {balance}")
else:
print("Недостаточно средств")
def deposit(amount):
global balance
with lock:
balance += amount
print(f"Внесено {amount}, новый баланс: {balance}")
## Создаем и запускаем потоки
withdraw_thread = threading.Thread(target=withdraw, args=(500,))
deposit_thread = threading.Thread(target=deposit, args=(200,))
withdraw_thread.start()
deposit_thread.start()
## Ждем завершения всех потоков
withdraw_thread.join()
deposit_thread.join()
Эти примеры демонстрируют, как могут возникать ситуации гонки в многопоточной программе на Python и как использовать методы синхронизации, такие как mutex, для их предотвращения. Изучение и применение этих концепций позволит вам писать более надежные и устойчивые параллельные приложения на Python.