Threads vs Processos no Linux

Recentemente, ouvi algumas pessoas dizerem que, no Linux, é quase sempre melhor usar processos em vez de threads, já que o Linux é muito eficiente no tratamento de processos e porque há muitos problemas (como bloqueio) associados a threads. No entanto, eu sou suspeito, porque parece que os tópicos podem dar um ganho de desempenho bastante grande em algumas situações.

Então, a minha pergunta é, quando confrontado com uma situação em que os processos e threads poderiam lidar muito bem, devo usar processos ou threads? Por exemplo, se eu estivesse escrevendo um servidor web, deveria usar processos ou threads (ou uma combinação)?

   

O Linux usa um modelo de thread 1-1, com (para o kernel) nenhuma distinção entre processos e threads – tudo é simplesmente uma tarefa executável. *

No Linux, o clone chamada de sistema clona uma tarefa, com um nível configurável de compartilhamento, entre as quais estão:

  • CLONE_FILES : compartilham a mesma tabela de descritores de arquivos (em vez de criar uma cópia)
  • CLONE_PARENT : não configure um relacionamento pai-filho entre a nova tarefa e a antiga (caso contrário, o getppid() da criança = getppid() pai / mãe)
  • CLONE_VM : compartilha o mesmo espaço de memory (em vez de criar uma cópia COW )

fork() chama clone( menos compartilhamento ) e pthread_create() chama clone( mais compartilhamento ) . **

fork custa um pouquinho mais do que o pthread_create ing por causa da cópia de tabelas e criação de mapeamentos COW para memory, mas os desenvolvedores do kernel Linux tentaram (e conseguiram) minimizar esses custos.

Alternar entre tarefas, se elas compartilharem o mesmo espaço de memory e várias tabelas, será um pouco mais barato do que se elas não fossem compartilhadas, porque os dados já podem estar carregados no cache. No entanto, as tarefas de comutação ainda são muito rápidas mesmo que nada seja compartilhado – isso é algo que os desenvolvedores de kernel do Linux tentam garantir (e conseguem garantir).

Na verdade, se você estiver em um sistema multiprocessador, não compartilhar pode realmente ser benéfico para o desempenho: se cada tarefa estiver sendo executada em um processador diferente, a synchronization da memory compartilhada será cara.


* Simplificado. CLONE_THREAD faz com que a entrega de sinais seja compartilhada (o que precisa de CLONE_SIGHAND , que compartilha a tabela do manipulador de sinal).

** Simplificado. Existem os SYS_fork e SYS_clone , mas no kernel, o sys_fork e o sys_clone são ambos muito finos em torno da mesma function do_fork , que em si é um wrapper em torno do copy_process . Sim, os termos process , thread e task são usados ​​de forma intercambiável no kernel do Linux …

O Linux (e de fato o Unix) oferece uma terceira opção.

Opção 1 – processos

Crie um executável autônomo que manipule alguma parte (ou todas as partes) do seu aplicativo e chame-o separadamente para cada processo, por exemplo, o programa executa cópias de si mesmo para delegar tarefas.

Opção 2 – tópicos

Crie um executável autônomo que inicie com um único thread e crie threads adicionais para executar algumas tarefas

Opção 3 – garfo

Disponível apenas no Linux / Unix, isso é um pouco diferente. Um processo bifurcado é realmente seu próprio processo com seu próprio espaço de endereço – não há nada que a criança possa fazer (normalmente) para afetar o espaço de endereço de seus pais ou irmãos (ao contrário de um encadeamento) – para que você obtenha mais robustez.

No entanto, as páginas de memory não são copiadas, elas são copy-on-write, portanto, menos memory é normalmente usada do que você imagina.

Considere um programa de servidor da web que consiste em duas etapas:

  1. Ler dados de configuração e tempo de execução
  2. Atender solicitações de páginas

Se você usasse threads, a etapa 1 seria feita uma vez e a etapa 2, feita em várias threads. Se você usasse processos “tradicionais”, as etapas 1 e 2 precisariam ser repetidas para cada processo e a memory para armazenar os dados de configuração e tempo de execução duplicados. Se você usou fork (), poderá executar a etapa 1 uma vez e, em seguida, fork (), deixando os dados de tempo de execução e a configuração na memory, intocados, não copiados.

Portanto, existem realmente três opções.

Isso depende de muitos fatores. Os processos são mais pesados ​​do que os encadeamentos e têm um custo maior de boot e encerramento. A comunicação entre processos (IPC – Interprocess Communication) também é mais difícil e mais lenta que a comunicação interthread.

Por outro lado, os processos são mais seguros e mais seguros do que os encadeamentos, porque cada processo é executado em seu próprio espaço de endereço virtual. Se um processo trava ou tem um buffer saturado, ele não afeta nenhum outro processo, enquanto que, se um encadeamento falha, ele retira todos os outros encadeamentos no processo e, se um encadeamento tiver uma saturação de buffer, ele será aberto. um buraco de segurança em todos os segmentos.

Portanto, se os módulos de seu aplicativo puderem ser executados na maioria das vezes de forma independente e com pouca comunicação, você provavelmente deverá usar processos se puder arcar com os custos de boot e desligamento. O impacto no desempenho do IPC será mínimo e você será um pouco mais seguro contra bugs e falhas de segurança. Se você precisar de todo o desempenho que puder obter ou ter muitos dados compartilhados (como estruturas de dados complexas), use os threads.

Outros discutiram as considerações.

Talvez a diferença importante seja que, no Windows, os processos são pesados ​​e caros em comparação com os threads, e no Linux a diferença é muito menor, então a equação se equilibra em um ponto diferente.

Era uma vez o Unix, e nesse bom e velho Unix havia muita sobrecarga para processos, então o que algumas pessoas espertas faziam era criar threads, que compartilhariam o mesmo espaço de endereço com o processo pai e precisariam apenas de um contexto reduzido switch, o que tornaria a mudança de contexto mais eficiente.

Em um Linux contemporâneo (2.6.x) não há muita diferença no desempenho entre um switch de contexto de um processo comparado a um thread (somente o material de MMU é adicional para o thread). Existe o problema com o espaço de endereçamento compartilhado, o que significa que um ponteiro defeituoso em um encadeamento pode corromper a memory do processo pai ou outro encadeamento dentro do mesmo espaço de endereçamento.

Um processo é protegido pela MMU, portanto, um ponteiro defeituoso causará apenas um sinal 11 e nenhuma corrupção.

Eu usaria processos em geral (não muito sobrecarga de switch de contexto no Linux, mas proteção de memory devido a MMU), mas pthreads se eu precisasse de uma class de agendador em tempo real, que é uma xícara diferente de chá todos juntos.

Por que você acha que os threads têm um ganho de desempenho tão grande no Linux? Você tem algum dado para isso, ou é apenas um mito?

Quão fortemente acopladas são suas tarefas?

Se eles podem viver independentemente uns dos outros, então use processos. Se eles confiam um no outro, use os threads. Dessa forma, você pode matar e reiniciar um processo ruim sem interferir na operação das outras tarefas.

Eu tenho que concordar com o que você está ouvindo. Quando xhpl nosso cluster ( xhpl e outros), sempre obtemos um desempenho significativamente melhor com processos sobre encadeamentos.

A decisão entre thread / processo depende um pouco do que você irá usá-lo. Um dos benefícios de um processo é que ele tem um PID e pode ser eliminado sem também terminar o pai.

Para um exemplo do mundo real de um servidor web, o apache 1.3 costumava suportar apenas múltiplos processos, mas em 2.0 eles adicionavam uma abstração para que você pudesse alternar entre ambos. Os comentários parecem concordar que os processos são mais robustos, mas os threads podem dar um desempenho um pouco melhor (exceto para janelas em que o desempenho de processos é uma droga e você só quer usar threads).

Para complicar ainda mais, existe algo como armazenamento local de thread e memory compartilhada Unix.

O armazenamento local de thread permite que cada thread tenha uma instância separada de objects globais. A única vez que usei isso foi ao construir um ambiente de emulação no linux / windows, para código de aplicativo executado em um RTOS. No RTOS cada tarefa era um processo com seu próprio espaço de endereço, no ambiente de emulação, cada tarefa era um encadeamento (com um espaço de endereço compartilhado). Usando o TLS para coisas como singletons, pudemos ter uma instância separada para cada thread, assim como sob o ambiente RTOS ‘real’.

A memory compartilhada pode (obviamente) fornecer os benefícios de desempenho de ter vários processos acessando a mesma memory, mas com o custo / risco de ter que sincronizar os processos adequadamente. Uma maneira de fazer isso é fazer com que um processo crie uma estrutura de dados na memory compartilhada e, em seguida, envie um identificador para essa estrutura por meio da comunicação tradicional entre processos (como um canal nomeado).

No meu trabalho recente com o LINUX, uma coisa é estar ciente das bibliotecas. Se você estiver usando encadeamentos, certifique-se de que quaisquer bibliotecas que você possa usar em encadeamentos sejam seguras para encadeamento. Isso me queimou algumas vezes. Nota: a libxml2 não é segura para threads fora da checkbox. Ele pode ser compilado com thread safe, mas não é isso que você obtém com o aptitude install.

Para a maioria dos casos, eu preferiria processos em encadeamentos. os encadeamentos podem ser úteis quando você tem uma tarefa relativamente menor (tempo de execução do processo >> tomado por cada unidade de tarefa dividida) e há uma necessidade de compartilhamento de memory entre eles. Pense em uma grande matriz. Além disso, (offtopic), observe que, se a sua utilização da CPU for 100% ou próxima a ela, não haverá benefício de multithreading ou processamento. (na verdade, vai piorar)

Se você precisar compartilhar resources, você realmente deve usar threads.

Considere também o fato de que as alternâncias de contexto entre os encadeamentos são muito menos dispendiosas do que as alternâncias de contexto entre os processos.

Não vejo razão para ir explicitamente com processos separados, a menos que você tenha uma boa razão para fazê-lo (segurança, testes de desempenho comprovados, etc …)