kb.erickguedes.com
C#: Matching Engine — Projeto Mercado Financeiro

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:

  1. Atomicidade: ordem não pode ser parcialmente visível
  2. Consistência: saldo não pode ficar inconsistente
  3. Isolamento: matches não interferem entre si
  4. 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.