Paralelismo x Concorrência x Goroutines

-
Concorrência: é quando várias tarefas são iniciadas e gerenciadas de forma que elas possam progredir simultaneamente, mas não necessariamente estão sendo executadas ao mesmo tempo. É como ter várias pessoas na fila de um caixa, e o atendente alterna entre elas para que todas sejam atendidas aos poucos.
-
Paralelismo: é quando várias tarefas realmente são executadas ao mesmo tempo, em paralelo, em diferentes núcleos de um processador. Imagine várias caixas funcionando ao mesmo tempo, cada uma atendendo um cliente de forma independente.
-
O que são Threads (a nível de SO):
- Threads são unidades de execução fornecidas pelo sistema operacional. Eles permitem que um programa execute múltiplas tarefas ao mesmo tempo, utilizando múltiplos núcleos da CPU. Cada thread tem seu próprio conjunto de recursos, incluindo uma pilha de memória separada, o que leva a algumas características:
- Pesados em termos de recursos: Criar e gerenciar threads consome uma quantidade significativa de memória e processamento.
- Gerenciados pelo sistema operacional: Threads são agendados diretamente pelo sistema operacional, o que significa que há um certo [[Overheading]] no gerenciamento, principalmente quando há mudança de contexto (context switching) entre threads.
- Escalabilidade limitada: Criar milhares de threads pode sobrecarregar o sistema, já que cada uma demanda recursos consideráveis.
-
O que são Goroutines:
- Gorutines são unidades de execução gerenciadas pelo próprio runtime da linguagem Go, e não pelo sistema operacional. Elas são uma abstração leve de threads que tornam o modelo de concorrência em Go muito mais eficiente. Principais características:
- Leves em termos de recursos: Goroutines são extremamente baratas em termos de uso de memória, com um tamanho de pilha inicial de apenas 2 KB, e a pilha pode crescer ou encolher conforme necessário. Em uma thread convencional, o tamanho padrão inicial da pilha de uma thread varia entre 1 MB e 8 MB em sistemas como Windows e Linux. Essa abordagem é um dos principais motivos pelos quais Go é eficiente no gerenciamento de concorrência, já que o overhead de criar e gerenciar goroutines é muito menor do que o overhead de threads convencionais.
- Agendadas pelo runtime de Go: O runtime de Go possui seu próprio agendador (scheduler), que mapeia goroutines para os threads do sistema operacional de forma eficiente. Ele tenta evitar o overhead de trocas de contexto frequentes.
- Alta escalabilidade: É possível criar centenas de milhares de goroutines sem sobrecarregar o sistema. Isso é muito útil em aplicativos que requerem alta concorrência.
- Comunicação via Canais: Goroutines se comunicam umas com as outras através de canais (channels), o que facilita a passagem de informações de maneira segura e sem a necessidade de mecanismos complexos como bloqueios (locks) em muitos casos.
Qual problema as Goroutines resolvem?
- Entre outros, o principal problema que as Goroutines resolvem é o [[Overheading]] no gerenciamento de memória do sistema. Goroutines são projetadas para serem muito eficientes e ajudam a reduzir o overhead em comparação com threads tradicionais:
1. Leveza e Baixo Consumo de Memória
- Goroutines são extremamente leves em termos de consumo de memória. Enquanto uma thread típica pode começar ocupando cerca de 1 MB de memória, uma goroutine começa com apenas cerca de 2 KB. Isso significa que é possível criar milhares ou até milhões de goroutines sem sobrecarregar a memória do sistema.
- Isso reduz significativamente o overhead de memória que normalmente ocorre quando se tenta criar muitas threads.
2. Gerenciamento de Contexto Eficiente
- Menos Overhead de Context Switching: Diferente das threads, que precisam ser gerenciadas pelo sistema operacional, goroutines são gerenciadas pelo runtime do Go. Isso significa que o runtime pode fazer trocas de contexto entre goroutines de maneira mais rápida e eficiente do que o SO faz com threads.
- Em vez de depender de operações complexas de salvar e restaurar estados de processo como ocorre com threads, o runtime do Go realiza isso de forma muito mais otimizada.
3. M Scheduling (Modelo de Escalonamento)
- O runtime do Go usa um modelo de escalonamento M, onde M goroutines são mapeadas para N threads do sistema operacional. Isso permite que o Go escale muito bem o uso de goroutines, mapeando múltiplas tarefas para menos threads, reduzindo o overhead de criação e gerenciamento de threads.
- Com esse modelo, o runtime do Go pode balancear a carga de trabalho de forma mais inteligente e evitar a criação excessiva de threads, que resultaria em mais context switching e overhead.
4. Controle Automático de Bloqueio
- Quando uma goroutine bloqueia (por exemplo, ao esperar por uma operação de I/O), o runtime do Go pode automaticamente mudar para outra goroutine sem precisar de uma troca de contexto no nível do sistema operacional. Isso é muito mais rápido e eficiente do que o que acontece com threads tradicionais, onde a troca é gerenciada pelo SO e resulta em mais overhead.
- Isso permite que o sistema seja mais responsivo e lide melhor com tarefas simultâneas sem gastar muitos recursos.
Desvantagens das Goroutines em Relação às Threads
- Menos Controle de Baixo Nível: Diferente das threads, não é possível definir diretamente a prioridade ou afinidade de CPU de uma goroutine, o que pode ser necessário em algumas aplicações.
- Dependência da Coleta de Lixo: O garbage collector do Go pode causar pausas durante a execução para liberar memória, o que pode introduzir latências indesejadas em sistemas críticos de tempo real.
- Complexidade no Runtime: O gerenciamento de goroutines depende do runtime do Go, que pode se tornar um gargalo em situações de alta carga.
- Integração com Outros Sistemas: Integrar programas Go que usam goroutines com sistemas que utilizam threads tradicionais pode ser desafiador.
Resumo
Goroutines são leves, eficientes e bem adaptadas para concorrência em Go, permitindo que aplicações sejam mais rápidas e responsivas. No entanto, sua menor flexibilidade no controle de baixo nível, dependência de garbage collection e integração com outros sistemas podem ser desvantagens em alguns casos.