Home Artigos Concorrência: Goroutines x Threads

Concorrência: Goroutines x Threads

2
2
800

Quando falamos em concorrência na computação, estamos falando na composição de processos de execução independentes.

Uma das coisas mais difíceis de se realizar, enquanto programador, é desenvolver uma aplicação que utiliza os recursos do hardware onde esta sendo executada, de maneira eficiente. Os computadores atuais possuem diversos processadores, porém a maioria das linguagens de programação não dispõe de recursos para tirar proveito deles, pelo menos não de uma maneira fácil.

Uma das grandes features em Go é exatamente isso, o suporte a concorrência. Go nos provêm com as goroutines, que permitem desenvolver aplicações de forma concorrente e de maneira simplificada, e o melhor: as goroutines podem se comunicar entre elas.

Threads x Goroutine

“Ah mais Java também trabalha com threads.” Sim, só que uma goroutine não é uma thread. Em Java você pode executar milhares ou dezenas de milhares de threads. Em Go você pode executar milhões de goroutines.

As threads em Java são mapeadas diretamente para as threads do sistema operacional, e threads do s.o. são relativamente pesadas. Parte da razão pela qual as threads são tão pesadas são por causa do seu stack de tamanho fixo. Isso diminui muito o número de threads que você pode executar em uma única máquina virtual devido ao aumento da sobrecarga de memória.

Por outro lado, uma goroutine tem um stack segmentado que cresce conforme a demanda. Elas são chamadas de “Green Threads“, o que significa dizer que o agendamento é feito pelo runtime do Go e não pelo sistema operacional. O runtime transmite simultaneamente as goroutines em threads reais do sistema operacional, cujo número é controlado pela constante GOMAXPROCS, que geralmente fica setado para espelhar o número de processadores em seu sistema, maximizando assim potencial paralelismo.

A criação de uma goroutine exige pouca memória: apenas 2kB de espaço no stack. Elas crescem alocando e liberando o armazenamento no heap conforme necessário. Threads, por outro lado, começam em 1Mb (500 vezes mais).

Um servidor tratando requests pode, portanto, criar uma goroutine por solicitação, sem problemas, mas uma thread por solicitação acabará em OutOfMemoryError. E isso não é só Java – qualquer linguagem que use os threads do sistema operacional como o principal meio de concorrência terá os mesmos problemas.

Outra desvantagem em se programar com threads para aplicar concorrência em larga escala é a complexidade e a manutenção de bases de código. Pode haver os temidos deadlocks e as race conditions, e identificar isso no código pode ser quase impossível.

Go disponibiliza primitivas que permitem que você evite totalmente os locks. O mantra é: não comunicar ao compartilhar memória, compartilhar memória ao se comunicar. Ou seja, se duas goroutines precisam compartilhar dados, elas podem fazê-lo com segurança através de um channel (recurso do Go que permite troca de dados entre as goroutines, assunto pra outro post). Go lida com toda a parte de sincronização para você, o que torna muito mais difícil o surgimento de deadlocks.
Pois, tal como acontece com outras linguagens, é importante evitar o acesso simultâneo de recursos compartilhados por mais de uma goroutine. É melhor transferir dados entre goroutines usando channels, ou seja, não se comunique compartilhando memória; Compartilhe memória ao se comunicar. Aliás, esse é um dos provérbios Go.

Conclusão

E pra terminar, um pequeno resumo das vantagens das goroutines sobre as threads:

  • Você pode executar muito mais goroutines em um sistema típico do que threads;
  • Goroutines tem stacks segmentadas e dinâmicas;
  • Goroutines tem um runtime mais rápido que threads;
  • Goroutines vêm com primitivas integradas para se comunicar com segurança entre elas (channels);
  • Goroutines são transmitidas simultaneamente para um pequeno número de threads do S.O., em vez de um mapeamento de um para um;
  • Você pode desenvolver servidores utilizando concorrência massivamente sem ter que recorrer a programação orientada a eventos (complexidade do código alta e difícil compreensão).

 

2 Comentários

  1. Lucas Gomide

    2 de junho de 2017 at 18:14

    Fala Arthur, beleza? Antes de tudo, parabéns pelo post, gostei muito.

    Sobre o tema, não é o primeiro artigo que eu vejo sobre Go indicando o custo de Goroutines pro SO e comparando com as threads em Java.

    Mas eu gostaria muito de saber como este custo é medido, donde vem este dado.

    Abs !

    Responder

    • Arthur Mastropietro

      Arthur Mastropietro

      3 de junho de 2017 at 01:11

      Fala Lucas! Então, em Java vc tem o VisualVM por exemplo que monitora e analisa as aplicações em tempo real, vc consegue ver quantas threads estão rodando, qual consumo de cada. Em Go vc tem algumas “tools” como o pacote net/http/pprof que vc consegue também monitorar.
      Em termos de tamanho, como cito no post, uma thread consome 1mb de memória, uma goroutine consome 2kb.
      Quando uma thread fica bloqueada outra thread precisa ser agendada. Durante esse agendamento cada thread salva e recupera 16 registradores diferentes.
      Uma goroutine quando agenda outra goroutine só lida com 3 registradores.
      Outra coisa, uma thread precisa requisitar recursos do SO pra rodar e depois, devolver. Uma goroutine é criada e descartada em tempo de execução.
      Sendo assim, tem uma série de coisas que diferem goroutines e threads. Importante lembrar que thread e goroutine não são a mesma coisa. Go também usa threads do SO. A diferença é que Java mapeia 1 thread do programa pra 1 thread do SO. Go multiplexa varias goroutines em uma thread.
      Espero ter ajudado. Abs!

      Responder

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Veja Também

Validação de CPF e CNPJ em Go

Simples e direto. Funções para validar CPF e CNPJ em Go. …