Threads vs Processos no Linux

253

Recentemente, ouvi algumas pessoas dizerem que, no Linux, quase sempre é melhor usar processos em vez de threads, pois o Linux é muito eficiente no processamento de processos e porque há muitos problemas (como bloqueio) associados a threads. No entanto, desconfio, porque parece que os threads podem proporcionar um ganho de desempenho bastante grande em algumas situações.

Portanto, minha pergunta é: quando confrontados com uma situação que threads e processos possam lidar muito bem, devo usar processos ou threads? Por exemplo, se eu estivesse escrevendo um servidor Web, devo usar processos ou threads (ou uma combinação)?

user17918
fonte
Existe alguma diferença com o Linux 2.4?
Mouviciel 30/04/09
3
A diferença entre processos e encadeamentos no Linux 2.4 é que os encadeamentos compartilham mais partes de seus estados (espaço de endereço, identificadores de arquivos etc.) do que processos, o que geralmente não acontece. O NPTL no Linux 2.6 torna isso um pouco mais claro, fornecendo a eles "grupos de encadeamentos", que são um pouco como "processos" no win32 e Solaris.
MarkR
6
A programação simultânea é difícil. A menos que você precise de um desempenho muito alto, o aspecto mais importante em sua troca geralmente será a dificuldade de depuração . Os processos facilitam a solução a esse respeito, porque toda a comunicação é explícita (fácil de verificar, registrar etc.). Por outro lado, a memória compartilhada de threads cria zilhões de lugares em que um thread pode afetar erroneamente outro.
Lutz Prechelt 10/09/2015
1
@LutzPrechelt - A programação simultânea pode ser multiencadeada e multiprocessos. Não vejo por que você está assumindo que a programação simultânea é apenas multiencadeada. Pode ser por causa de algumas limitações específicas de idioma, mas, em geral, podem ser as duas coisas.
iankit
2
Eu vinculo Lutz apenas afirmando que a programação simultânea é difícil, independentemente da escolha - processo ou threads -, mas que a programação simultânea usando processos facilita a depuração em muitos casos.
User2692263

Respostas:

322

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

No Linux, a chamada do sistema clone clona uma tarefa, com um nível configurável de compartilhamento, entre os quais:

  • CLONE_FILES: compartilhe a mesma tabela de descritor de arquivo (em vez de criar uma cópia)
  • CLONE_PARENT: não estabeleça uma relação pai-filho entre a nova tarefa e a antiga (caso contrário, getppid() = pai getpid())
  • CLONE_VM: compartilhe o mesmo espaço de memória (em vez de criar um COW cópia )

fork()chamadas clone(menos compartilhamento )e pthread_create()chamadasclone( mais compartilhamento ). **

forkIsso custa um pouco mais do que o custo pthread_createpor causa da cópia de tabelas e da criação de mapeamentos de COW para memória, mas os desenvolvedores do kernel do Linux tentaram (e conseguiram) minimizar esses custos.

A alternância de tarefas, se compartilharem o mesmo espaço de memória e várias tabelas, será um pouco mais barata do que se não forem compartilhadas, porque os dados já podem estar carregados no cache. No entanto, a alternância de tarefas ainda é muito rápida, mesmo que nada seja compartilhado - isso é algo que os desenvolvedores de kernel do Linux tentam garantir (e conseguem garantir).

De fato, se você estiver em um sistema com vários processadores, o compartilhamento não será realmente benéfico para o desempenho: se cada tarefa estiver sendo executada em um processador diferente, a sincronização da memória compartilhada será cara.


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

** simplificado. Existem dois SYS_forke SYS_clonesyscalls, mas no kernel, o sys_forke sys_clonesão invólucros muito finos com a mesma do_forkfunção, que por si só é um invólucro fino copy_process. Sim, os termos process, threade tasksão usados em vez de forma intercambiável no kernel do Linux ...

efémero
fonte
6
Acho que estamos perdendo 1 ponto. Se você criar vários processos para o servidor da Web, precisará escrever outro processo para abrir o soquete e passar o 'trabalho' para diferentes segmentos. A segmentação oferece um único processo com várias threads, design limpo. Em muitas situações, o encadeamento é simplesmente natural e, em outra situação, um novo processo é simplesmente natural. Quando o problema cai em uma área cinzenta, as outras compensações, conforme explicadas pelo epemiente, se tornam importantes.
Saurabh
26
@Saurabh Na verdade não. Você pode facilmente socket, bind, listen, fork, e, em seguida, ter vários processos acceptconexões no mesmo soquete de audição. Um processo pode parar de aceitar se estiver ocupado, e o kernel encaminhará as conexões de entrada para outro processo (se ninguém estiver ouvindo, o kernel irá enfileirar ou soltar, dependendo da listenlista de pendências). Você não tem muito mais controle sobre a distribuição de trabalho do que isso, mas geralmente isso é bom o suficiente!
ephemient 26/03
2
@Bloodcount Todos os processos / threads no Linux são criados pelo mesmo mecanismo, que clona um processo / thread existente. Sinalizadores passados ​​para clone()determinar quais recursos são compartilhados. Uma tarefa também pode unshare()recursos a qualquer momento posterior.
ephemient 18/06
4
@KarthikBalaguru Dentro do próprio kernel, existe um task_structpara cada tarefa. Isso geralmente é chamado de "processo" em todo o código do kernel, mas corresponde a cada thread executável. Não existe process_struct; se vários task_structs estão vinculados por sua thread_grouplista, eles são o mesmo "processo" para o espaço do usuário. Há um pouco de manipulação especial de "threads" s, por exemplo, todos os threads irmãos são interrompidos no fork e no exec, e apenas o thread "main" aparece ls /proc. Cada thread é acessível via /proc/pid, seja ele listado /procou não.
ephemient 29/09/14
5
@KarthikBalaguru O kernel suporta um continuum de comportamento entre threads e processos; por exemplo, clone(CLONE_THREAD | CLONE_VM | CLONE_SIGHAND))forneceria um novo "encadeamento" que não compartilha o diretório de trabalho, arquivos ou bloqueios, enquanto clone(CLONE_FILES | CLONE_FS | CLONE_IO)o "processo" que o faz. O sistema subjacente cria tarefas por clonagem; fork()e pthread_create()são apenas funções de biblioteca que invocam de maneira clone()diferente (como escrevi nesta resposta).
ephemient 29/09/14
60

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

Opção 1 - processos

Crie um executável independente que lide com 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 - threads

Crie um executável independente que seja iniciado 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 (diferente de um encadeamento) - para obter mais robustez.

No entanto, as páginas da memória não são copiadas, elas são copiadas na gravação; portanto, geralmente é usada menos memória 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ágina

Se você usasse threads, a etapa 1 seria executada uma vez e a etapa 2, executada em vários threads. Se você usou processos "tradicionais", as etapas 1 e 2 precisariam ser repetidas para cada processo, e a memória para armazenar os dados de configuração e tempo de execução duplicados. Se você usou o fork (), poderá executar a etapa 1 uma vez e depois o fork (), deixando os dados e a configuração do tempo de execução na memória, intocados, não copiados.

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

MarkR
fonte
7
O @Qwertie bifurcação não é tão legal, ele quebra muitas bibliotecas de maneiras sutis (se você as usar no processo pai). Cria um comportamento inesperado que confunde até programadores experientes.
precisa saber é
2
O @MarkR poderia dar alguns exemplos ou um link de como o bifurcação quebra a biblioteca e cria um comportamento inesperado?
Ehtesh Choudhury
18
Se um processo se bifurca com uma conexão mysql aberta, coisas ruins acontecem, pois o soquete é compartilhado entre dois processos. Mesmo que apenas um processo use a conexão, o outro impede que ela seja fechada.
MarkR
1
A chamada do sistema fork () é especificada pelo POSIX (o que significa que está disponível em qualquer sistema Unix). Se você usou a API do Linux subjacente, que é a chamada do sistema clone (), na verdade você tem ainda mais opções no Linux do que apenas as três. .
Lie Ryan
2
@ MarkR O compartilhamento do soquete é por design. Além disso, qualquer um dos processos pode fechar o soquete usando linux.die.net/man/2/shutdown antes de chamar close () no soquete.
Lelanthran
53

Isso depende de muitos fatores. Os processos são mais pesados ​​que os threads e têm um custo mais alto de inicialização e desligamento. A comunicação entre processos (IPC) também é mais difícil e mais lenta que a comunicação entre segmentos.

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

Portanto, se os módulos do seu aplicativo puderem ser executados de forma independente e com pouca comunicação, você provavelmente deverá usar processos se puder arcar com os custos de inicialização e desligamento. O impacto no desempenho do IPC será mínimo e você ficará 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 threads.

Adam Rosenfield
fonte
9
A resposta de Adam serviria bem como um briefing executivo. Para mais detalhes, MarkR e ephemient fornecem boas explicações. Uma explicação muito detalhada com exemplos pode ser encontrada em cs.cf.ac.uk/Dave/C/node29.html, mas parece estar um pouco datada em partes.
CyberFonic
2
O CyberFonic é verdadeiro para Windows. Como diz o epemiente, no Linux, os processos não são mais pesados. E no Linux, todos os mecanismos disponíveis para a comunicação entre threads (futex, memória compartilhada, pipes, IPC) também estão disponíveis para processos e são executados na mesma velocidade.
Russell Stuart
O IPC é mais difícil de usar, mas e se alguém usar "memória compartilhada"?
Abhiarora
11

Outros discutiram as considerações.

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

dmckee --- gatinho ex-moderador
fonte
9

Era uma vez o Unix e, nesse bom e velho Unix, havia muita sobrecarga para os processos; portanto, o que algumas pessoas inteligentes faziam era criar threads, que compartilhavam o mesmo espaço de endereço com o processo pai e eles só precisavam de um contexto reduzido. switch, o que tornaria o contexto mais eficiente.

Em um Linux contemporâneo (2.6.x), não há muita diferença no desempenho entre uma alternância de contexto de um processo em comparação com um encadeamento (apenas o material da MMU é adicional para o encadeamento). Há um problema com o espaço de endereço compartilhado, o que significa que um ponteiro com defeito em um thread pode corromper a memória do processo pai ou outro thread no mesmo espaço de endereço.

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

Em geral, eu usava processos (não há muita sobrecarga de troca de contexto no Linux, mas proteção de memória devido à MMU), mas processa se precisar de uma classe de agendador em tempo real, que é uma xícara de chá diferente.

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?

robert.berger
fonte
1
Sim, eu tenho alguns dados. Fiz um teste que cria 100.000 processos e um teste que cria 100.000 threads. A versão do encadeamento foi executada 9x mais rápido (17,38 segundos para processos, 1,93 para encadeamentos). Agora, isso apenas testa o tempo de criação, mas para tarefas de curta duração, o tempo de criação pode ser essencial.
User17918
4
@ user17918 - É possível que você compartilhe o código usado por você para calcular os tempos mencionados acima ..
codingfreak
uma grande diferença, com os processos do kernel criar tabela de páginas para cada processo e theads usar apenas um tabelas de páginas, então eu acho que é normal as roscas são mais rápidos, em seguida, processa
c4f4t0r
Outra maneira simples de ver isso é o TCB é bem menor que o PCB e, portanto, é óbvio que a troca de contexto de processo que envolve PCB consumirá um pouco mais de tempo do que a troca de threads.
Karthik Balaguru
5

Quão fortemente acopladas são suas tarefas?

Se eles podem viver independentemente um do outro, use processos. Se eles dependem um do outro, use threads. Dessa forma, você pode matar e reiniciar um processo ruim sem interferir na operação das outras tarefas.

Robert
fonte
4

Para complicar ainda mais, existe o armazenamento local de threads e a memória compartilhada do Unix.

O armazenamento local do encadeamento permite que cada encadeamento tenha uma instância separada de objetos globais. A única vez que o usei 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). Ao usar o TLS para coisas como singletons, pudemos ter uma instância separada para cada thread, exatamente como no ambiente RTOS 'real'.

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

KeyserSoze
fonte
1
Usei o armazenamento local do encadeamento para algumas estatísticas, a última vez que escrevi um programa de redes encadeadas: cada encadeamento gravava em seus próprios contadores, não eram necessários bloqueios e somente quando o sistema enviava mensagens, cada encadeamento combinava suas estatísticas aos totais globais. Mas sim, o TLS não é muito usado ou necessário. Memória compartilhada, por outro lado ... além de enviar dados com eficiência, você também pode compartilhar semáforos POSIX entre processos, colocando-os na memória compartilhada. É incrível.
ephemient
4

No meu trabalho recente com o LINUX, uma coisa é estar ciente das bibliotecas. Se você estiver usando threads, verifique se as bibliotecas que você pode usar nos threads são seguras. Isso me queimou algumas vezes. Nota-se libxml2 não é seguro para threads imediatamente. Ele pode ser compilado com thread safe, mas não é isso que você obtém com a instalação do aptitude.

aal8
fonte
3

Eu teria que concordar com o que você está ouvindo. Quando avaliamos nosso cluster ( xhple assim por diante), sempre obtemos desempenho significativamente melhor com processos sobre threads.</anecdote>

eduffy
fonte
3

A decisão entre encadeamento / processo depende um pouco do que você usará. Um dos benefícios de um processo é que ele possui um PID e pode ser eliminado sem também encerrar o pai.

Para um exemplo do mundo real de um servidor Web, o apache 1.3 costumava suportar apenas vários processos, mas no 2.0 eles adicionaram uma abstração para que você pudesse alternar entre eles. Comentários parece que concordam que os processos são mais robustos, mas tópicos podem dar um pouco melhor desempenho (exceto para as janelas onde o desempenho para processos suga e você só quer usar threads).

hlovdal
fonte
2

Na maioria dos casos, eu preferiria processos sobre threads. os encadeamentos podem ser úteis quando você possui uma tarefa relativamente menor (sobrecarga do processo >> tempo gasto por cada unidade de tarefa dividida) e há necessidade de compartilhamento de memória entre eles. Pense em uma grande variedade. Além disso (offtopic), observe que, se a utilização da sua CPU for 100% ou próxima dela, não haverá benefícios no multithreading ou no processamento. (de fato, vai piorar)

neal aise
fonte
Como assim, nenhum benefício? Que tal realizar cálculos pesados ​​no thread da GUI? Movê-los para um encadeamento paralelo será muito melhor do ponto de vista da experiência do usuário, independentemente do carregamento da CPU.
olegst
2

Threads -> Threads compartilha um espaço de memória, é uma abstração da CPU, é leve. Processos -> Processos têm seu próprio espaço de memória, é uma abstração de um computador. Para paralelizar tarefas, você precisa abstrair uma CPU. No entanto, as vantagens de usar um processo sobre um encadeamento são segurança, estabilidade, enquanto um encadeamento usa menos memória que o processo e oferece menor latência. Um exemplo em termos de web seria chrome e firefox. No caso do Chrome, cada guia é um novo processo, portanto, o uso de memória do chrome é maior que o firefox, enquanto a segurança e a estabilidade oferecidas são melhores que o firefox. A segurança aqui fornecida pelo chrome é melhor, pois cada guia é um novo processo. Uma guia diferente não pode bisbilhotar o espaço de memória de um determinado processo.

Jubin Antony Thykattil
fonte
2

Acho que todo mundo fez um ótimo trabalho respondendo à sua pergunta. Estou apenas adicionando mais informações sobre thread versus processo no Linux para esclarecer e resumir algumas das respostas anteriores no contexto do kernel. Portanto, minha resposta está relacionada ao código específico do kernel no Linux. De acordo com a documentação do Linux Kernel, não há distinção clara entre encadeamento e processo, exceto que o encadeamento usa espaço de endereço virtual compartilhado, diferente do processo. Observe também que o kernel do Linux usa o termo "tarefa" para se referir ao processo e ao encadeamento em geral.

"Não há estruturas internas implementando processos ou threads; em vez disso, existe uma estrutura task_struct que descreve uma unidade de agendamento abstrata chamada task"

Também de acordo com Linus Torvalds, você NÃO deve pensar em processo versus encadeamento e porque é muito limitante e a única diferença é COE ou Contexto de Execução em termos de "separar o espaço de endereço do pai" ou o espaço de endereço compartilhado. De fato, ele usa um exemplo de servidor da Web para mostrar seu ponto aqui (o que recomenda a leitura).

Crédito total para documentação do kernel Linux

grepit
fonte
-3

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

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

Não vejo razão para usar explicitamente processos separados, a menos que você tenha um bom motivo para fazê-lo (segurança, testes de desempenho comprovados, etc ...)

Yuval Adam
fonte
3
Eu tenho o representante para editar, mas não concordo totalmente. O contexto alterna entre processos no Linux é quase tão barato quanto o contexto alterna entre threads.
ephemient