Threads de Núcleo e de Usuário
As características e funcionamento do processamento utilizando-se threads de núcleo e/ou usuário, suas diferenças e aplicações.
Para o bom desenvolvimento da programação assíncrona, na minha opinião, é fundamental o conhecimento sobre o funcionamento das threads de núcleo e de usuário.
Afinal, são essas estruturas que permitem que o assincronismo demonstre todo o seu poder. E, apesar de sua importância, muitos desenvolvedores desconhecem os princípios tanto de hardware quanto de software por trás delas.
Em tempos onde há um poder computacional crescente visto ano a ano, temos em conjunto, uma demanda cada vez maior pelo melhor aproveitamento dessas capacidades. Isso não somente por redução de custos (imperativo de negócio), mas também pelo aumento do número de aplicações compartilhando os mesmos recursos (imperativo técnico).
O que são Threads
Threads (linhas de execução) são um conjunto de instruções que permitem um processo executar tarefas de forma paralela e/ou concorrente.
Elas tem sua própria pilha de execução, compartilham o espaço de endereçamento e todos os dados do processo a que estão associadas.
Elas são uma abstração poderosa, que proporcionou aos sistemas operacionais a dar maior flexibilidade e agilidade na execução dos processos, além de um melhor uso dos recursos.
Nos servidores web multithreading, por exemplo, são as responsáveis por atenderem requisições de maneira simultânea.
E, como podemos ver na imagem abaixo, a quantidade de threads utilizadas pelo SO supera em grande vantagem a de processos.
Tipos de Threads
Existem três tipos de threads, as de núcleo, as de usuário e as híbridas. Para fins de simplificação, trataremos das duas primeiras neste artigo.
Threads de núcleo (kernel-level thread)
Esse tipo de thread é gerenciado totalmente pelo SO, que fica responsável pela criação, informações de contexto, escalonamento e sincronização. Ele também irá manter a tabela de processos, bem como de todas as threads.
Dessa forma, em um ambiente multiprocessado, o escalonador do SO tentará tirar proveito dos núcleos/CPUs disponíveis para uso.
É preciso ter em mente que tanto a criação quanto as trocas de contexto são custosas (ainda que menos do que nos processos), por isso, o reaproveitamento de threads é bastante utilizado (ex.: thread pool).
A vantagem nesse caso, é que a aplicação (processo) não precisa se preocupar com o gerenciamento.
Temos ainda, o fato de que se uma thread fizer uma chamada bloqueante, as outras poderão continuar executando.
Threads de usuário (user-level thread)
Ao contrário do cenário anterior, o processo fica a cargo de todo o gerenciamento das threads, normalmente, através da utilização de uma biblioteca para esse fim.
Nesse tipo, o processo é capaz de se valer de mecanismos de escalonamento mais adequados para determinadas tarefas. Outro benefício é que o chaveamento de contextos tem menor custo.
Das desvantagens, temos que muitas das chamadas ao SO são bloqueantes, o que faz com que todo o processo e, por sua vez suas threads, sejam colocadas em suspensão.
Outro ponto, é que em um modelo de ULT puro (não híbrido), o SO irá atribuir apenas uma CPU para o processo.
E como ficamos em .NET?
Na plataforma .NET, temos diversas bibliotecas nativas para trabalharmos com threads. Elas abstraem grande parte do problema de gerenciamento e sincronização com APIs mais inteligíveis.
Mas, mesmo com essas “facilidades”, compreender o quê e como acontece por baixo dos panos é muito importante.
O que achou? Conta pra gente nos comentários.
Veja também: Difference between Multiprogramming, multitasking, multithreading and multiprocessing
Excelente!
Muito boa a explicação.