Melhor resposta
A resposta à pergunta colocada é: é o mesmo que a diferença entre “um “e” muitos “.
Mas acho que o que você provavelmente quis dizer é algo mais parecido com isto: como as metodologias de programação diferem para a programação em um único thread em um único processo em comparação com vários threads em um único processo?
Livros foram escritos sobre o assunto. Mas, resumidamente, todas as diferenças se relacionam a um fator: ao programar em um único thread e processo, você sempre sabe como chegou à instrução atual. As instruções ocorrem em uma única sequência. Mas ao programar para muitos threads, você não tem ideia de quais instruções foram executadas em que ordem para chegar à instrução atual. Isso torna a programação mais complicada.
Mas há um outro fator que a complica: ao contrário dos processos, os threads compartilham memória. Você não sabe qual thread tocou pela última vez em um determinado local de memória, ou qual será o próximo, a menos que tenha algum tipo de “sincronização”. Daí a palavra-chave “synchronized” em Java (arrancada de Ada). Monitores, semáforos, bloqueios , variáveis de condição e até mesmo passagem de mensagens foram usadas para sincronização.
Resposta
(Atualização: atente para outra resposta que diz que você não pode ter um servidor web de thread único que lida bem com solicitações simultâneas porque isso simplesmente não é verdade.)
Por que um servidor web multithread é melhor do que um servidor de thread único? Não é.
Existem quatro maneiras básicas de como um servidor da web pode lidar com a simultaneidade:
- bifurcação um processo de SO por solicitação (como versões antigas do Apache)
- gerando uma thread de SO por solicitação (como uma nova versão do Apache)
- usando um loop de evento de thread único (como nginx )
- usando fios verdes ou processos leves agendada por um tempo de execução VM em vez do sistema operacional (como em Erlang)
Atualmente, as abordagens mais comuns são as de número 2 e 3.
Existem prós e contras de ambos deles. Para operações vinculadas a E / S (uma característica de um servidor da web típico), você obtém melhor desempenho e maior número de solicitações simultâneas quando você usa um loop de evento de thread único . Mas a desvantagem é que você precisa usar E / S não bloqueadora exclusivamente assíncrona para todas as operações ou, caso contrário, você bloqueará o loop de eventos e perderá desempenho. Por esse motivo, é mais fácil implementar um servidor multithread, mas você paga em desempenho.
Para operações vinculadas à CPU (menos comum para um servidor web comum, talvez mais comum para uma API de computação intensiva), é melhor ter um thread de SO ou processo por núcleo . É fácil de fazer com loops de evento de thread único porque você pode executar um cluster de vários processos, um por núcleo. É difícil de fazer com servidores multi-threaded porque se spawning threads é sua única maneira de lidar com solicitações simultâneas, então você não pode realmente controlar quantos threads você terá – já que você não controla o número de solicitações. Quando você tem mais threads do que o número de núcleos de CPU, perde desempenho para alternâncias de contexto e também usa muita RAM.
É por isso que um servidor nginx de thread único tem melhor desempenho do que um servidor da web Apache com vários threads (e é por isso que o nginx foi criado em primeiro lugar). Além disso, Redis , um banco de dados conhecido por desempenho excepcionalmente alto é single-threaded .
Um exemplo real que posso dar é este: meu primeiro servidor web foi o Apache rodando em uma máquina Linux com 500 MB de RAM. Ele bifurcou um novo processo para cada solicitação (na verdade, tinha um pool, então não havia muita bifurcação envolvida, mas precisava manter esses processos ativos para reutilizá-los e eliminá-los de vez em quando para evitar vazamento de recursos).
Meu SO usou cerca de 100 MB de RAM. Cada processo do Apache usou 20 MB de RAM. Isso significava que meu servidor só podia lidar com 20 solicitações simultâneas e não havia maneira de contornar isso porque eu não tinha mais RAM. A maioria dos processos estava bloqueada no I / O, então a utilização da CPU era muito baixa, todas as solicitações acima desses 20 tinham que esperar e, se esses 20 eram, por exemplo, downloads demorados, então meu servidor estava completamente sem resposta.
Quando o servidor da web nginx foi introduzido, ele usava um loop de evento de thread único e não bloqueava para nenhuma solicitação. Ele poderia lidar com muito mais solicitações simultâneas, sem problemas com o mítico problema c10k – nginx foi criado basicamente para resolver o problema c10k (10.000 solicitações simultâneas).
Imagine quanta RAM é desperdiçada em 10.000 threads se você pode até gerar tantos e quanto tempo é usado para mudanças de contexto.
Uso de memória de Apache multi-threaded vs nginx de thread único:
A propósito, esta é a razão pela qual Ryan Dahl usou um I / O sem bloqueio e um loop de evento de thread único no Node.js e ele ainda usa a mesma ideia no Deno, porque essa é a maneira de escrever servidores de rede de alto desempenho (ao contrário do que você pode ler em outras respostas aqui).