Threads e Servidores Web
Autor: Rhuan Victor C. de Santiago | Publicado: 2011-09-08T21:03:00-07:00

O interessante sobre esse assunto é que sempre quando se fala em Threads, cita-se o exemplo de servidores web. Saindo do paradigma de servidores web como o apache, é importante termos em mente que nem todos os servidores web utilizam-se de threads para atender as suas requisições. Por exemplo:&nbsp;nginx,&nbsp;node.js e medusa. &nbsp;Qual a idéia por trás dessas implementação? &nbsp;Continue lendo...

Existem três maneiras comuns de se tratar uma requisição:



sincronamente: manupula uma requisição por vez.&nbsp;

pros: simples&nbsp;
contras: toda requisição nova, terá que esperar as outras terminarem.&nbsp;

novo processo: cria um novo processo para manipular cada requisição.&nbsp;

pros: fácil de implementar&nbsp;
contras: não escala muito bem, centanas de conexões significam centenas de processos.&nbsp;

threads: nova Thread para cada requisição.





A maioria já conhecem como funcionam as threads, farei apenas uma breve introdução para exemplificar como um servidor web baseado em threads é implementado hoje em dia.



Threads.



Thread é a execução de sub-rotinas de um programa executadas concorrentemente pelo processador. Podem ser classificadas em dois níveis: usuário ou kernel. Threads em nível kernel são threads implementadas a nível de sistema operacional. Em modo usuario significa que o framework ou linguagem de programacao que voce está usando implementa o conceito.




Vantagens

Mais rápidas do que criar novos processos, pois nao necessita de alocação de recursos, já que compartilham o mesmo espaço de endereçamento.&nbsp;
Concorrência entre subrotinas do processo.&nbsp;
Real paralelismo em máquinas multicore(apenas threads kernel).


Desvantagens

Acesso compartilhado aos recursos.&nbsp;
Threads em modo usuário ao realizarem i/o bloqueiam o processo inteiro.



Outro problema com threads em SO ruins, é que criar é devagar. Solaris e Windows podem criar threads mais rápidos que processos, mas ainda continua devagar. Então o que os servidores web baseados em thread fizeram? Eles inventaram as thread pools.

Uma thread pool funciona como uma pre criação. As threads são criadas em blocos no começo do programa e o trabalho é distribuido eventualmente. Quando existem mais conexões do que threads, as novas conexões precisam esperar. Mas pelo lado positivo, você evitou o custo de criação. Isso não parece ser muito escalável, parece?


Exemplo em pseudo código que exemplifica a utilização de threads por servidores web.


Thread para despacho de requisições.


enquanto(verdade) {
   Obter_proximo_pedido( &amp;buf );
   Iniciar_Worker_Thread( buf );
}



Thread para atender requisições.

enquanto(verdade) {
   Esperar_Trabalho ( &amp;buf );
   Look_for_page_cache(&amp;buf,&amp;page);
   se (page_not_in_cache (&amp;page)
       Read_page_from_disk( &amp;buf, &amp;page);
   retornar _page( &amp;page );
}




Para ler um pouco mais sobre threads, veja essa apresentação:


'Event-drive' programming&nbsp;

Esse é um outro paradigma que foge aqueles três citados no começo desse post.&nbsp;Nesse método você escalona a cpu e os recursos que quiser aplicar&nbsp;para&nbsp;cada conexão. Como fazer isso depende da sua aplicação e nem sempre é possível ou sensato fazer isso. Mas se você conseguir capturar o estado(sessão) de cada conexão e dividi-las em pequenas peças, então essa solução será melhor aplicada. Se as suas conexões não requerem muito ou nenhuma seção, então esta é a ideia perfeita para você.



pros:&nbsp;

eficiente e elegante&nbsp;
escala bem - centenas de conexões dizem somente centenas objectos de socket/estado e não uma centenas de processos.&nbsp;
não requer barramentos em recursos compartilhados.&nbsp;
se integra facilmente com a api do windows baseada em eventos.&nbsp;

contras:&nbsp;

mais complexo - você tem que criar uma máquina de estados.&nbsp;
requer um paradigma de programação diferente que pode ser confuso de inicio.



Então... como funciona?&nbsp;


Para explicar esse fato, usarei as palavras referidas por&nbsp;Sam Rushing.

Imagine o seu programa como uma pessoa. Imagine também que existe dois baldes vazios, um na frente e um atrás. O balde da frente será enchido com água e o trabalho da pessoa é esperar que esse balde fique cheio e o despeje no balde de trás. O balde frontal enche-se esporadicamente, algumas vezes muito rápido,e algumas vezes muito devagar, mas em geral o seu programa fica parado a maioria do tempo.





Agora, e se o seu programa precisa se comunicar com mais de uma conexão ou arquivo ao mesmo tempo? Criando um novo processo é equivalente a colocar uma nova pessoa para cada par de baldes. Um típico servidor é escrito dessa forma! Um servidor pode estar manipulando 20 conexões simultâneas, &nbsp;e na nossa metáfora isso significa uma linha de 20 pessoas ociosas, 99% do tempo, cada uma esperando o seu balde encher!





A solução óbvia para esse problema é ter uma única pessoa andando para cima e para baixo entre os pares de baldes. Quando a pessoa perceber que um balde está cheio, ela o despeja no balde de trás e continua se movendo. Por andar para cima e para baixo, uma pessoa ocupada faz o trabalho de 20 pessoas ociosas.





A única vez em que esta solução não funcionará bem é quando alguma outra coisa que simplesmente despejar a água de um balde no outro precisa ser feita - tal como transformar a água em ouro, primeiramente. Se transformar um balde de água em um balde de ouro leva muito tempo, então os outros baldes podem não ser processados em um tempo adequado.&nbsp;Por exemplo, se o seu servidor precisa tratar os dados recebidos antes de responder.



Referências

http://msdn.microsoft.com/pt-br/library/kztecsys.aspx
http://www.nightmare.com/medusa/async_sockets.html
http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/
http://stackoverflow.com/questions/3629784/how-is-node-js-inherently-faster-when-it-still-relies-on-threads-internally
 

http://www.yuiblog.com/blog/2010/05/20/video-dahl/