Saltar al contenido principal

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​

MecanismoAlcanceUso
Critical SectionIntra-procesoAcceso rápido, mismo proceso
MutexInter-procesoExclusión mutua, nombrado
SemaphoreInter-procesoControl de acceso múltiple
EventInter-procesoSeñalización
InterlockedIntra-procesoOperaciones 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​

TipoNombreAlcance
AnónimoNingunoThreads 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​

MutexSemaphore
1 solo accesoN accesos simultáneos
Propietario únicoSin 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​

TipoComportamiento
AutoResetLibera un solo thread y luego vuelve a no señalado
ManualResetPermanece 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ónDescripción
IncrementIncrementa en 1
DecrementDecrementa en 1
AddSuma un valor
ExchangeIntercambia valores
CompareExchangeIntercambio 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​

  1. Orden constante: Siempre adquirir los locks en el mismo orden
  2. Timeouts: Utilizar timeouts para detectar bloqueos
  3. 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​

MecanismoRendimientoCaso de uso
InterlockedMuy rápidoOperaciones simples
Critical SectionRápidoMismo proceso
MutexMedioInter-proceso
SemaphoreMedioAcceso 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 Count
  • Thread > Context Switches/sec

Ejercicios​

  1. Cree un script con dos jobs PowerShell que utilicen un mutex compartido
  2. Implemente un contador thread-safe con Interlocked
  3. Cree un semaphore que limite a 2 ejecuciones simultáneas

Referencias​