Ejemplos Prácticos y Soluciones
Para ilustrar mejor los conceptos de condiciones de carrera y las técnicas para prevenir las mismas, exploremos algunos ejemplos prácticos y soluciones.
Ejemplo 1: Incremento del Contador
Supongamos que tenemos un contador compartido que varios hilos necesitan incrementar. Sin una sincronización adecuada, puede ocurrir una condición de carrera, lo que conduce a un recuento final incorrecto.
import threading
## Contador compartido
counter = 0
def increment_counter():
global counter
for _ in range(1000000):
counter += 1
## Crear y comenzar los hilos
threads = [threading.Thread(target=increment_counter) for _ in range(4)]
for thread in threads:
thread.start()
## Esperar a que todos los hilos terminen
for thread in threads:
thread.join()
print(f"Valor final del contador: {counter}")
Para prevenir la condición de carrera, podemos utilizar un Mutex para garantizar que solo un hilo pueda acceder al contador compartido a la vez:
import threading
## Contador compartido
counter = 0
lock = threading.Lock()
def increment_counter():
global counter
for _ in range(1000000):
with lock:
counter += 1
## Crear y comenzar los hilos
threads = [threading.Thread(target=increment_counter) for _ in range(4)]
for thread in threads:
thread.start()
## Esperar a que todos los hilos terminen
for thread in threads:
thread.join()
print(f"Valor final del contador: {counter}")
Ejemplo 2: Cuenta Bancaria Compartida
Consideremos un escenario donde varios hilos están accediendo a una cuenta bancaria compartida. Sin una sincronización adecuada, puede ocurrir una condición de carrera, lo que conduce a saldos de cuenta incorrectos.
import threading
## Cuenta bancaria compartida
balance = 1000
def withdraw(amount):
global balance
if balance >= amount:
balance -= amount
print(f"Retiró {amount}, nuevo saldo: {balance}")
else:
print("Fondos insuficientes")
def deposit(amount):
global balance
balance += amount
print(f"Depositó {amount}, nuevo saldo: {balance}")
## Crear y comenzar los hilos
withdraw_thread = threading.Thread(target=withdraw, args=(500,))
deposit_thread = threading.Thread(target=deposit, args=(200,))
withdraw_thread.start()
deposit_thread.start()
## Esperar a que todos los hilos terminen
withdraw_thread.join()
deposit_thread.join()
Para prevenir la condición de carrera, podemos utilizar un Mutex para garantizar que solo un hilo pueda acceder a la cuenta bancaria compartida a la vez:
import threading
## Cuenta bancaria compartida
balance = 1000
lock = threading.Lock()
def withdraw(amount):
global balance
with lock:
if balance >= amount:
balance -= amount
print(f"Retiró {amount}, nuevo saldo: {balance}")
else:
print("Fondos insuficientes")
def deposit(amount):
global balance
with lock:
balance += amount
print(f"Depositó {amount}, nuevo saldo: {balance}")
## Crear y comenzar los hilos
withdraw_thread = threading.Thread(target=withdraw, args=(500,))
deposit_thread = threading.Thread(target=deposit, args=(200,))
withdraw_thread.start()
deposit_thread.start()
## Esperar a que todos los hilos terminen
withdraw_thread.join()
deposit_thread.join()
Estos ejemplos demuestran cómo pueden ocurrir condiciones de carrera en la programación multihilo de Python y cómo utilizar técnicas de sincronización, como los Mutex, para prevenir las mismas. Al entender y aplicar estos conceptos, puede escribir aplicaciones concurrentes más confiables y robustas en Python.