Sincronización de Procesos y Threads
Introducción
Cuando varios threads o procesos acceden a recursos compartidos, es necesario coordinar sus accesos para evitar problemas de concurrencia.
Problemas de Concurrencia
Race Condition (Condición de carrera)
Cuando el resultado depende de un orden de ejecución no determinado.
Thread 1 Thread 2
-------- --------
Leer contador (= 5)
Leer contador (= 5)
Incrementar (= 6)
Incrementar (= 6)
Escribir (= 6)
Escribir (= 6)
Resultado: ¡6 en vez de 7!
Sección Crítica
Zona de código que accede a un recurso compartido y debe ser ejecutada por un solo thread a la vez.
Deadlock (Interbloqueo)
Situación donde dos threads se esperan mutuamente indefinidamente.
Thread 1: Posee A, espera B
Thread 2: Posee B, espera A
-> ¡Bloqueo permanente!
Mecanismos de Sincronización en Windows
Visión general
| Mecanismo | Alcance | Uso |
|---|---|---|
| Critical Section | Intra-proceso | Acceso rápido, mismo proceso |
| Mutex | Inter-proceso | Exclusión mutua, nombrado |
| Semaphore | Inter-proceso | Control de acceso múltiple |
| Event | Inter-proceso | Señalización |
| Interlocked | Intra-proceso | Operaciones atómicas simples |
Critical Section
Concepto
Mecanismo ligero para sincronizar threads dentro de un mismo proceso.
Características
- Más rápido que Mutex
- Limitado a un solo proceso
- No nombrado
Uso (C/C++)
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
EnterCriticalSection(&cs);
// Sección crítica - un solo thread a la vez
compteur++;
LeaveCriticalSection(&cs);
DeleteCriticalSection(&cs);
En PowerShell (simulación)
# Uso de [System.Threading.Monitor]
$lockObj = New-Object Object
$compteur = 0
[System.Threading.Monitor]::Enter($lockObj)
try {
# Sección crítica
$compteur++
}
finally {
[System.Threading.Monitor]::Exit($lockObj)
}
Mutex (Mutual Exclusion)
Concepto
Objeto de sincronización que permite la exclusión mutua entre procesos.
Características
- Puede ser nombrado (accesible entre procesos)
- Más lento que Critical Section
- Detección de deadlock posible
Uso en PowerShell
# Crear o abrir un mutex nombrado
$mutexName = "Global\MonMutex"
$mutex = New-Object System.Threading.Mutex($false, $mutexName)
try {
# Esperar el mutex (timeout 5 segundos)
if ($mutex.WaitOne(5000)) {
try {
Write-Host "Mutex adquirido - sección crítica"
# Trabajo exclusivo aquí
Start-Sleep -Seconds 2
}
finally {
$mutex.ReleaseMutex()
Write-Host "Mutex liberado"
}
}
else {
Write-Host "Timeout - mutex no adquirido"
}
}
finally {
$mutex.Close()
}
Mutex nombrado vs anónimo
| Tipo | Nombre | Alcance |
|---|---|---|
| Anónimo | Ninguno | Threads del mismo proceso |
| Local | "Local\Nombre" | Sesión de usuario |
| Global | "Global\Nombre" | Todos los procesos del sistema |
Semaphore
Concepto
Permite limitar el número de accesos simultáneos a un recurso.
Diferencia con Mutex
| Mutex | Semaphore |
|---|---|
| 1 solo acceso | N accesos simultáneos |
| Propietario único | Sin propietario |
Uso en PowerShell
# Semaphore con 3 lugares
$semName = "Global\MonSemaphore"
$semaphore = New-Object System.Threading.Semaphore(3, 3, $semName)
try {
if ($semaphore.WaitOne(5000)) {
try {
Write-Host "Lugar adquirido en el semaphore"
# Trabajo limitado a 3 threads simultáneos
Start-Sleep -Seconds 2
}
finally {
$semaphore.Release()
Write-Host "Lugar liberado"
}
}
}
finally {
$semaphore.Close()
}
Casos de uso
- Pool de conexiones a base de datos
- Limitación de descargas simultáneas
- Control de recursos limitados
Event
Concepto
Permite a un thread señalar un evento a otros threads.
Tipos de events
| Tipo | Comportamiento |
|---|---|
| AutoReset | Libera un solo thread y luego vuelve a no señalado |
| ManualReset | Permanece señalado hasta reset explícito |
Uso en PowerShell
# Event ManualReset
$eventName = "Global\MonEvent"
$event = New-Object System.Threading.EventWaitHandle(
$false, # Estado inicial: no señalado
[System.Threading.EventResetMode]::ManualReset,
$eventName
)
# En el thread productor
$event.Set() # Señalar
$event.Reset() # Volver a no señalado
# En el thread consumidor
if ($event.WaitOne(5000)) {
Write-Host "¡Evento recibido!"
}
$event.Close()
Interlocked Operations
Concepto
Operaciones atómicas sobre variables, sin necesidad de lock explícito.
Operaciones disponibles
| Operación | Descripción |
|---|---|
| Increment | Incrementa en 1 |
| Decrement | Decrementa en 1 |
| Add | Suma un valor |
| Exchange | Intercambia valores |
| CompareExchange | Intercambio condicional |
Uso en PowerShell
# Incremento atómico
$compteur = 0
[System.Threading.Interlocked]::Increment([ref]$compteur)
Write-Host "Contador: $compteur"
# Suma atómica
[System.Threading.Interlocked]::Add([ref]$compteur, 10)
Write-Host "Contador después de add: $compteur"
# Intercambio atómico
$oldValue = [System.Threading.Interlocked]::Exchange([ref]$compteur, 100)
Write-Host "Valor anterior: $oldValue, Nuevo: $compteur"
Buenas Prácticas
Evitar los Deadlocks
- Orden constante: Siempre adquirir los locks en el mismo orden
- Timeouts: Utilizar timeouts para detectar bloqueos
- Minimizar: Mantener las secciones críticas cortas
Ejemplo de deadlock evitado
# MALO - riesgo de deadlock
# Thread 1: Lock A luego Lock B
# Thread 2: Lock B luego Lock A
# BUENO - orden constante
# Thread 1: Lock A luego Lock B
# Thread 2: Lock A luego Lock B (¡mismo orden!)
Rendimiento
| Mecanismo | Rendimiento | Caso de uso |
|---|---|---|
| Interlocked | Muy rápido | Operaciones simples |
| Critical Section | Rápido | Mismo proceso |
| Mutex | Medio | Inter-proceso |
| Semaphore | Medio | Acceso múltiple limitado |
Detección de Problemas
Process Explorer
- View > Show Lower Pane
- Handle view: ver los handles de sincronización
WinDbg
!locks # Mostrar los locks
!handle # Mostrar los handles
!analyze -hang # Analizar un hang/deadlock
Performance Monitor
Contadores útiles:
Process > Handle CountThread > Context Switches/sec
Ejercicios
- Cree un script con dos jobs PowerShell que utilicen un mutex compartido
- Implemente un contador thread-safe con Interlocked
- Cree un semaphore que limite a 2 ejecuciones simultáneas