Concorrência na Matching Engine
Aula 4 de 7
Desafios de Concorrência
Uma Matching Engine recebe ordens de múltiplos clientes simultaneamente. Precisamos garantir:
- Atomicidade: ordem não pode ser parcialmente visível
- Consistência: saldo não pode ficar inconsistente
- Isolamento: matches não interferem entre si
- Performance: baixa latência (microssegundos)
Locking com Monitor
public class MatchingEngine
{
private readonly ConcurrentDictionary<string, OrderBook> _books = new();
private readonly object _globalLock = new();
public TradeResult EnviarOrdem(Ordem ordem)
{
var book = _books.GetOrAdd(ordem.Simbolo, s => new OrderBook(s));
lock (_globalLock) // lock coarse-grained
{
book.AdicionarOrdem(ordem);
var trades = book.ExecutarMatches();
return new TradeResult(ordem.Id, trades);
}
}
}
Lock-Free com Interlocked
// Sequenciador de IDs sem lock
public class IdGenerator
{
private long _nextId;
public IdGenerator(long start = 1)
{
_nextId = start;
}
public long Next()
=> Interlocked.Increment(ref _nextId);
}
// Gerenciamento de saldo lock-free
public class SaldoConta
{
private long _saldoCentavos; // armazenar como inteiro
public bool Debitar(long valorCentavos)
{
var original = Interlocked.Read(ref _saldoCentavos);
while (original >= valorCentavos)
{
var novo = original - valorCentavos;
var resultado = Interlocked.CompareExchange(
ref _saldoCentavos, novo, original);
if (resultado == original)
return true;
original = resultado; // outro thread mudou, tentar novamente
}
return false; // saldo insuficiente
}
public void Creditar(long valorCentavos)
=> Interlocked.Add(ref _saldoCentavos, valorCentavos);
}
ReaderWriterLock — Leitura Otimizada
public class OrderBookSnapshot
{
private readonly ReaderWriterLockSlim _rwLock = new();
public MarketData ObterSnapshot()
{
_rwLock.EnterReadLock();
try
{
// Leitura segura — múltiplos leitores simultâneos
return _currentSnapshot;
}
finally
{
_rwLock.ExitReadLock();
}
}
public void AtualizarSnapshot()
{
_rwLock.EnterWriteLock();
try
{
// Apenas um escritor por vez
_currentSnapshot = GerarSnapshot();
}
finally
{
_rwLock.ExitWriteLock();
}
}
}
SemaphoreSlim — Rate Limiting
public class RateLimiter
{
private readonly SemaphoreSlim _semaphore;
public RateLimiter(int maxConcurrent)
{
_semaphore = new SemaphoreSlim(maxConcurrent);
}
public async Task<T> ExecutarComLimite<T>(Func<Task<T>> action)
{
await _semaphore.WaitAsync();
try
{
return await action();
}
finally
{
_semaphore.Release();
}
}
}
Channel — Producer/Consumer
// Pipeline assíncrono de ordens
public class OrderPipeline
{
private readonly Channel<Ordem> _channel;
public OrderPipeline()
{
_channel = Channel.CreateBounded<Ordem>(
new BoundedChannelOptions(10000)
{
FullMode = BoundedChannelFullMode.Wait
});
}
public async Task IniciarProcessamentoAsync(
MatchingEngine engine, CancellationToken ct)
{
await foreach (var ordem in _channel.Reader.ReadAllAsync(ct))
{
// Processar em lote para throughput
var resultado = engine.EnviarOrdem(ordem);
NotificarCliente(resultado);
}
}
}
Prefira lock-free (Interlocked) para operações simples. Use ReaderWriterLockSlim para leituras frequentes. Channel é ideal para pipelines assíncronos.