O Truque Que Faz Aplicações Escalarem
Um dos conceitos mais importantes para construir aplicações escaláveis é entender como usar concorrência corretamente.
Diferentes tipos de carga de trabalho exigem estratégias diferentes.
- Operações I/O-bound devem usar async/await
- Operações CPU-bound devem usar processamento paralelo
Usar a abordagem correta permite que aplicações lidem com mais trabalho sem desperdiçar recursos do sistema.
Operações de I/O: Use Async/Await
Para operações de I/O como chamadas de API, consultas a banco de dados ou acesso a arquivos, utilize async/await.
Essas operações passam a maior parte do tempo esperando sistemas externos. A programação assíncrona permite que a thread seja devolvida para o Thread Pool enquanto a operação de I/O está em andamento.
Isso evita que threads fiquem bloqueadas e permite que o sistema processe mais requisições de forma concorrente.
Tarefas Pesadas de CPU: Use Processamento Paralelo
Para tarefas intensivas de CPU, como processamento de grandes volumes de dados ou cálculos complexos, o processamento paralelo pode melhorar significativamente o desempenho.
No .NET, um exemplo comum é:
Parallel.ForEach(items, item =>
{
Process(item);
});
Isso distribui o trabalho entre múltiplos núcleos da CPU, permitindo que tarefas sejam executadas simultaneamente.
Antes de usar paralelismo, é importante verificar se o servidor possui múltiplos núcleos de CPU disponíveis.
Prefira Task em vez de Criar Threads Manualmente
Em aplicações modernas .NET, desenvolvedores raramente criam threads manualmente. Em vez disso, utilizam Task, que é uma abstração de nível mais alto integrada ao Thread Pool.
Tasks oferecem melhor gerenciamento de recursos e funcionam naturalmente com os padrões de programação assíncrona e paralela.
Entendendo Threads
Para entender por que essas técnicas melhoram a escalabilidade, é importante entender o que é uma thread.
O que é uma Thread?
Uma thread é a menor unidade de execução dentro de um processo.
Ela representa uma sequência de instruções que a CPU executa. Threads permitem que programas executem múltiplas tarefas de forma concorrente.
Threads de CPU (Hardware)
Uma thread de CPU é uma unidade de execução em hardware dentro do processador capaz de executar um fluxo de instruções.
CPUs modernas possuem:
- Múltiplos núcleos
- Múltiplas threads por núcleo (por exemplo com Hyper-Threading)
Exemplo:
- 4 núcleos
- 8 threads de hardware
Isso significa que a CPU pode executar até 8 fluxos de instruções concorrentemente, dependendo da carga de trabalho e do agendamento.
Threads na Programação
Na programação, uma thread é uma abstração de software que representa uma sequência de instruções que desejamos executar de forma independente.
Quando criamos uma thread em código, estamos solicitando que o escalonador do sistema operacional aloque tempo de CPU para executar aquela tarefa.
Exemplo em C#:
new Thread(() =>
{
Console.WriteLine("Hello world");
}).Start();
new Thread(() =>
{
Console.WriteLine("Hello world");
}).Start();
Essas threads podem executar concorrentemente dependendo de como o sistema operacional as agenda.
Threads no Nível do Sistema Operacional
No nível do sistema operacional, uma thread continua sendo a menor unidade de execução dentro de um processo.
O sistema operacional é responsável por:
- Agendar threads
- Associá-las às threads de hardware da CPU
- Gerenciar sua execução
- Realizar troca de contexto (context switching)
Modelo de Memória das Threads
Cada thread possui seu próprio:
- Stack – usado para variáveis locais e chamadas de método
- Registradores da CPU – armazenamento temporário durante execução
- Instruction Pointer – indica a próxima instrução a ser executada
- Estado de execução – rodando, esperando ou bloqueada
No entanto, threads dentro do mesmo processo compartilham o mesmo espaço de memória.
Esse compartilhamento permite colaboração entre threads, mas também pode causar problemas de concorrência como race conditions.
Resumo
Aplicações de alta performance escalam ao utilizar a estratégia correta para cada tipo de trabalho:
- Async/await para operações I/O-bound
- Processamento paralelo para tarefas CPU-bound
- Task e Thread Pool em vez de gerenciar threads manualmente
Compreender esses conceitos é essencial para construir sistemas backend escaláveis.