Green threads
Last updated
Last updated
Quando falamos de threads, geralmente estamos nos referindo a threads do sistema operacional, ou kernel threads. Algumas implementações de linguagens de programação, como o GCC em C, permitem utilizar kernel threads, que no caso do C é através da função pthread_create
.
Mas como vimos no tópico sobre os desafios com o uso de kernel threads, a gestão de milhares de threads pode aumentar consideravelmente a latência do sistema.
Como alternativa, algumas implementações optam por usar abstrações similar a threads dentro do próprio runtime, pelo que precisam desenvolver o próprio escalonador de threads.
A essas threads que vivem dentro do runtime, ou como costumamos dizer, a nível do usuário, damos o nome de green threads.
Mas antes de falarmos das green threads, vamos relembrar o funcionamento de uma kernel thread.
Como já aprendemos nos tópicos anteriores, em C conseguimos criar threads do sistema operacional, ou kernel threads. Basicamente, uma kernel thread tem uma pilha onde guarda o estado de execução entre outros metadados:
Em C, a chamada da função pthread_create
basicamente cria uma kernel thread, então a gestão de escalonamento e troca de contexto fica completamente a cargo do kernel (SO), e não do GCC:
Uma green thread geralmente tem uma estrutura similar a uma kernel thread, contendo sua própria pilha de memória mas compartilhando a memória principal do processo.
Mas uma green thread tem a vantagem de não levar com a latência da criação de kernel threads, ou seja, dependendo da implementação de green threads no runtime, podemos criar milhares, senão milhões de green threads mantendo baixa latência.
A nível de implementação, em determinado momento vamos precisar "associar" as green threads às kernel threads, afinal:
Todo programa roda em uma thread principal, ou seja, para o escalonador do sistema operacional é tudo thread, ou melhor ainda, é tudo task
O desafio é então multiplexar um número arbitrário de green threads para kernel threads, e isto pode ser feito de várias formas. Vamos a seguir detalhar alguns tipos comuns de implementação de green threads.
Neste tipo de implementação, temos apenas uma thread principal do programa que é mapeada diretamente para uma kernel thread. E dentro desta thread, criamos múltiplas green threads:
A vantagem desta abordagem é que temos menos latência comparando com o uso indiscriminado de kernel threads, mas por outro lado, a desvantagem é que não temos um paralelismo real mesmo em CPU multi-core, pois o sistema operacional não sabe que se trata de uma green thread, então no fim das contas é apenas uma thread sendo utilizada na CPU.
Esta abordagem é muito comum no runtime Go, e é basicamente a capacidade de criar uma kernel thread para um conjunto específico de green threads:
A vantagem nisso é que podemos utilizar mais paralelismo, uma vez que cada kernel thread pode utilizar um núcleo de CPU. Se o runtime for espertinho o suficiente, temos uma situação muito interessante para lidar com concorrência e paralelismo.
Alô Gophers, o momento de vocês está chegando :P
Outra abordagem muito interessante, e que é utilizada na implementação do Erlang/Elixir (BEAM), é que poderíamos multiplexar kernel threads pra cada escalonador. Assim, cada escalonador iria cuidar de um número X de green threads mantendo o paralelismo real.
Mas a ideia por enquanto não é falar de Go nem Erlang, pois estamos ainda no C. Chegou o momento de entender como podemos trabalhar com green threads em C.
Infelizmente, a implementação padrão do GCC não traz suporte a green threads. O que até faz sentido, imagina o C trazer isso? Seria overkill, sendo uma linguagem bastante genérica, de propósito geral e muito perto do sistema operacional.
Mas há algumas bibliotecas externas que trazem este conceito, onde podemos ter "green threads" em C contando com uma estrutura de escalonamento das threads:
libdill.org: green threads (chamadas na lib de corrotinas), com comunicação baseada em canais e escalonamento cooperativo
libmill.org: similar à libdill, mas implementada de forma mais robusta e escalável, com multiplexing de kernel threads semelhante ao que temos em Go
...entre outras, como libtask, libcoro, GNU pth etc