Sincronização de Processos e Threads
Introdução
Quando múltiplos threads ou processos acessam recursos compartilhados, é necessário coordenar seus acessos para evitar problemas de concorrência.
Problemas de Concorrência
Race Condition (Condição de corrida)
Quando o resultado depende de uma ordem de execução indeterminada.
Thread 1 Thread 2
-------- --------
Ler contador (= 5)
Ler contador (= 5)
Incrementar (= 6)
Incrementar (= 6)
Escrever (= 6)
Escrever (= 6)
Resultado: 6 em vez de 7!
Seção Crítica
Área de código que acessa um recurso compartilhado e deve ser executada por apenas um thread por vez.
Deadlock (Interbloqueio)
Situação em que dois threads ficam esperando um ao outro indefinidamente.
Thread 1: Detém A, espera B
Thread 2: Detém B, espera A
-> Bloqueio permanente!
Mecanismos de Sincronização no Windows
Visão geral
| Mecanismo | Escopo | Utilização |
|---|---|---|
| Critical Section | Intra-processo | Acesso rápido, mesmo processo |
| Mutex | Inter-processo | Exclusão mútua, nomeado |
| Semaphore | Inter-processo | Controle de múltiplos acessos |
| Event | Inter-processo | Sinalização |
| Interlocked | Intra-processo | Operações atômicas simples |
Critical Section
Conceito
Mecanismo leve para sincronizar threads dentro de um mesmo processo.
Características
- Mais rápido que Mutex
- Limitado a um único processo
- Não nomeado
Utilização (C/C++)
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
EnterCriticalSection(&cs);
// Seção crítica - apenas um thread por vez
contador++;
LeaveCriticalSection(&cs);
DeleteCriticalSection(&cs);
Em PowerShell (simulação)
# Utilização de [System.Threading.Monitor]
$lockObj = New-Object Object
$contador = 0
[System.Threading.Monitor]::Enter($lockObj)
try {
# Seção crítica
$contador++
}
finally {
[System.Threading.Monitor]::Exit($lockObj)
}
Mutex (Exclusão Mútua)
Conceito
Objeto de sincronização que permite exclusão mútua entre processos.
Características
- Pode ser nomeado (acessível entre processos)
- Mais lento que Critical Section
- Possível detectar deadlock
Utilização em PowerShell
# Criar ou abrir um mutex nomeado
$mutexName = "Global\MonMutex"
$mutex = New-Object System.Threading.Mutex($false, $mutexName)
try {
# Esperar pelo mutex (timeout 5 segundos)
if ($mutex.WaitOne(5000)) {
try {
Write-Host "Mutex adquirido - seção crítica"
# Trabalho exclusivo aqui
Start-Sleep -Seconds 2
}
finally {
$mutex.ReleaseMutex()
Write-Host "Mutex liberado"
}
}
else {
Write-Host "Timeout - mutex não adquirido"
}
}
finally {
$mutex.Close()
}
Mutex nomeado vs anônimo
| Tipo | Nome | Escopo |
|---|---|---|
| Anônimo | Nenhum | Threads do mesmo processo |
| Local | "Local\Nome" | Sessão do usuário |
| Global | "Global\Nome" | Todos os processos do sistema |
Semaphore
Conceito
Permite limitar o número de acessos simultâneos a um recurso.
Diferença em relação ao Mutex
| Mutex | Semaphore |
|---|---|
| 1 único acesso | N acessos simultâneos |
| Proprietário único | Sem proprietário |
Utilização em PowerShell
# Semaphore com 3 vagas
$semName = "Global\MonSemaphore"
$semaphore = New-Object System.Threading.Semaphore(3, 3, $semName)
try {
if ($semaphore.WaitOne(5000)) {
try {
Write-Host "Vaga adquirida no semaphore"
# Trabalho limitado a 3 threads simultâneos
Start-Sleep -Seconds 2
}
finally {
$semaphore.Release()
Write-Host "Vaga liberada"
}
}
}
finally {
$semaphore.Close()
}
Casos de uso
- Pool de conexões de banco de dados
- Limitação de downloads simultâneos
- Controle de recursos limitados
Event
Conceito
Permite que um thread sinalize um evento para outros threads.
Tipos de events
| Tipo | Comportamento |
|---|---|
| AutoReset | Libera um único thread e volta ao estado não sinalizado |
| ManualReset | Permanece sinalizado até reset explícito |
Utilização em PowerShell
# Event ManualReset
$eventName = "Global\MonEvent"
$event = New-Object System.Threading.EventWaitHandle(
$false, # Estado inicial: não sinalizado
[System.Threading.EventResetMode]::ManualReset,
$eventName
)
# No thread produtor
$event.Set() # Sinalizar
$event.Reset() # Voltar para não sinalizado
# No thread consumidor
if ($event.WaitOne(5000)) {
Write-Host "Evento recebido!"
}
$event.Close()
Interlocked Operations
Conceito
Operações atômicas em variáveis, sem necessidade de lock explícito.
Operações disponíveis
| Operação | Descrição |
|---|---|
| Increment | Incrementa em 1 |
| Decrement | Decrementa em 1 |
| Add | Adiciona um valor |
| Exchange | Troca valores |
| CompareExchange | Troca condicional |
Utilização em PowerShell
# Incremento atômico
$contador = 0
[System.Threading.Interlocked]::Increment([ref]$contador)
Write-Host "Contador: $contador"
# Add atômico
[System.Threading.Interlocked]::Add([ref]$contador, 10)
Write-Host "Contador após add: $contador"
# Exchange atômico
$oldValue = [System.Threading.Interlocked]::Exchange([ref]$contador, 100)
Write-Host "Valor antigo: $oldValue, Novo: $contador"
Boas Práticas
Evitar Deadlocks
- Ordem constante: Sempre adquirir os locks na mesma ordem
- Timeouts: Usar timeouts para detectar bloqueios
- Minimizar: Manter as seções críticas curtas
Exemplo de deadlock evitado
# RUIM - risco de deadlock
# Thread 1: Lock A depois Lock B
# Thread 2: Lock B depois Lock A
# BOM - ordem constante
# Thread 1: Lock A depois Lock B
# Thread 2: Lock A depois Lock B (mesma ordem!)
Performance
| Mecanismo | Performance | Caso de uso |
|---|---|---|
| Interlocked | Muito rápido | Operações simples |
| Critical Section | Rápido | Mesmo processo |
| Mutex | Médio | Inter-processo |
| Semaphore | Médio | Acesso múltiplo limitado |
Detecção de Problemas
Process Explorer
- View > Show Lower Pane
- Handle view: ver os handles de sincronização
WinDbg
!locks # Exibir locks
!handle # Exibir handles
!analyze -hang # Analisar um hang/deadlock
Performance Monitor
Contadores úteis:
Process > Handle CountThread > Context Switches/sec
Exercícios
- Crie um script com dois jobs PowerShell que usam um mutex compartilhado
- Implemente um contador thread-safe com Interlocked
- Crie um semaphore que limite a 2 execuções simultâneas