Objetivo: Compreender o modelo de execução da Máquina Virtual Erlang (BEAM) e contrastá-lo com os modelos tradicionais de Threads de SO e Green Threads (Go).
1. O Problema da Concorrência Tradicional
Na engenharia de software clássica (Java, C++, C#), a unidade de concorrência é a Thread do Sistema Operacional (OS Thread).
• Peso: Cada thread consome uma quantidade significativa de memória (geralmente alguns MBs apenas para a stack).
• Gerenciamento: O Kernel do SO é responsável por agendar essas threads (Context Switching). Trocar de contexto no nÃvel do kernel é uma operação custosa (latência).
• Memória Compartilhada: Threads do mesmo processo compartilham o mesmo espaço de memória (Heap).
◦ O Perigo: Se a Thread A e a Thread B tentam escrever na variável x ao mesmo tempo, ocorre uma Condição de Corrida (Race Condition).
◦ A Solução (e o problema dela): Usamos Mutexes, Semáforos e Locks. Isso introduz complexidade e riscos de Deadlocks.
2. A Solução Elixir: O Modelo de Atores e a BEAM
O Elixir roda sobre a BEAM. A BEAM não utiliza threads do SO diretamente para executar sua lógica. Ela utiliza Processos Leves (Lightweight Processes).
2.1. O Processo da BEAM
• Peso: Extremamente leve (inicia com cerca de ~300 words ou ~2.5KB). Você pode rodar milhões deles em um laptop. • Isolamento de Memória (Share Nothing): Cada processo tem sua própria Heap e Stack. O Processo A não consegue acessar a memória do Processo B. • Comunicação: A única forma de interação é através de Troca de Mensagens (Message Passing). Os dados são copiados de um processo para outro (com exceção de binários grandes, que usam contagem de referência).
2.2. O Scheduler da BEAM (Escalonamento)
Isso é crucial para engenheiros: A BEAM roda, geralmente, uma Thread de SO por Núcleo de CPU. Dentro dessas threads, a BEAM roda seus próprios escalonadores (Schedulers). • Escalonamento Preemptivo: A BEAM atribui a cada processo um número de "reduções" (aproximadamente 2000 chamadas de função). Quando as reduções acabam, a BEAM pausa o processo forçadamente e dá a vez para o próximo. ◦ Vantagem: Um processo com loop infinito ou cálculo pesado não trava o sistema. Em Node.js (single-thread event loop), um cálculo pesado trava tudo. No Elixir, não.
3. Comparativo Técnico: Elixir vs. Go (Goroutines) vs. Java (Threads)
Esta é uma dúvida comum de mercado e academia.
| CaracterÃstica | Java / C# (OS Threads) | Go (Goroutines) | Elixir (BEAM Processes) |
|---|---|---|---|
| Gerenciamento | Kernel do SO | Runtime da linguagem (M:N) | Runtime da linguagem (M:N) |
| Uso de Memória | Alto (MBs) | Baixo (KBs) | Muito Baixo (KBs) |
| Memória | Compartilhada (precisa de mutex) | Compartilhada (mas encoraja channels) | Isolada — Share Nothing |
| Tolerância a Falhas | Se uma thread crashar, o processo pode cair. | Se houver panic não tratado, o programa cai. | Se um processo crashar, só ele morre. O sistema continua. |
| Latência | PrevisÃvel (limitada pelo SO) | Baixa | Soft Real-Time (latência muito previsÃvel) |
Goroutines (Go) e Processos Elixir (BEAM) são ambos green threads, mas com filosofias opostas:
4. Conceitos Fundamentais
Precisamos alinhar o vocabulário para as próximas etapas.
4.1. Concorrência vs. Paralelismo
• Concorrência: É sobre a estrutura do programa. É a capacidade de lidar com muitas coisas ao mesmo tempo. (Ex: Um servidor web aceitando 10 mil conexões). • Paralelismo: É sobre a execução fÃsica. É fazer muitas coisas no mesmo instante de tempo. Requer múltiplos núcleos de CPU. â—¦ Em Elixir: Você escreve código concorrente (muitos processos). A BEAM se encarrega de paralelizar isso automaticamente em todos os núcleos disponÃveis.
4.2. SÃncrono vs. AssÃncrono
Em sistemas distribuÃdos (e no Elixir): • SÃncrono (Call): Envio a mensagem e bloqueio minha execução até receber a resposta (Request/Response). • AssÃncrono (Cast): Envio a mensagem e sigo minha vida ("Fire and forget"). Não sei se a mensagem chegou ou se foi processada na hora.
4.3. Transparência de Localização (Sistemas DistribuÃdos)
Como processos se comunicam apenas por mensagens enviadas para um endereço (PID - Process ID), não importa onde esse processo está.
• Enviar mensagem para o PID <0.100.0> (na mesma máquina).
• Enviar mensagem para o PID <50.100.0> (em um servidor no Japão).
O código é exatamente o mesmo. Isso torna o Elixir uma linguagem naturalmente orientada a sistemas distribuÃdos (Clusters).
Objetivo: Manipular as primitivas de concorrência da BEAM (spawn, send, receive) e entender o ciclo de vida de um processo.
Abra seu terminal. Vamos criar um projeto chamado lab_concorrencia.
Isso carrega o ambiente do Mix, que será útil para compilar os módulos que criaremos mais à frente.
mix new lab_concorrencia
cd lab_concorrencia
iex -S mix