O que constitui o uso adequado de threads na programação?

13

Estou cansado de ouvir as pessoas recomendarem que você use apenas um thread por processador, enquanto muitos programas usam até 100 por processo! tome por exemplo alguns programas comuns

vb.net ide uses about 25 thread when not debugging
System uses about 100
chrome uses about 19
Avira uses more than about 50

Sempre que postar uma pergunta relacionada ao encadeamento, sou lembrado quase sempre que não devo usar mais de um encadeamento por processador e todos os programas mencionados acima estão arruinando o sistema com um único processador.

Smith
fonte
7
Essa recomendação é ampla. O limite de um encadeamento por processador é apropriado apenas para aplicativos vinculados computacionalmente. A maioria dos programas é vinculada a E / S, seja tráfego de rede, acesso ao disco ou até RAM. É por isso que servidores da web, bancos de dados etc. têm conjuntos de threads com muito mais threads do que núcleos de processador.
precisa
2
"Quase sempre sou lembrado que não devo usar mais de um thread por processador"? Você pode postar links ou exemplos? Quase toda vez?
29511 S.Lott
2
"... as pessoas recomendam que você use apenas um thread por processo." Quem são essas pessoas? A programação avançou significativamente desde a Idade das Trevas.
Rein Henrichs
2
Você não deve ter mais de um thread da interface do usuário por processo.
SLaks
3
@Billy ONeal, a sua edição fez a pergunta sem sentido
SK-logic

Respostas:

22

você deve usar apenas um thread por processador,

Possivelmente em HPC, onde você deseja a máxima eficiência - mas, do contrário, a coisa mais estúpida que ouvi hoje!

Você deve usar o número de threads que são apropriados para o design do programa e ainda fornecer desempenho aceitável.

Para um servidor Web, pode ser razoável disparar um encadeamento para cada conexão de entrada (embora haja maneiras melhores para servidores com muita carga).

Para um ide, cada ferramenta executada em seu próprio segmento não é irracional. Suspeito que muitos dos threads relatados para o .Net IDE sejam coisas como log e tarefas de E / S iniciadas em seus próprios threads, para que possam continuar desbloqueados.

Martin Beckett
fonte
9
Agora você está me perguntando qual é a coisa mais estúpida que você já ouviu!
Michael K
3
@ Michael - eu ensinei graduação e trabalhei em contratos de defesa - você não acreditaria nas coisas mais estúpidas que já ouvi!
Martin Beckett
1
Já os vimos no TheDailyWTF.com?
FrustratedWithFormsDesigner
i realmente não pode encontrá-los agora, mas olhada neste link social.msdn.microsoft.com/Forums/en-US/vbgeneral/thread/...
Smith
2
Ter no máximo um encadeamento vinculado à CPU por processador alocado para o aplicativo. Os threads vinculados à IO não são um grande problema (exceto a memória que consomem) e é importante lembrar que os aplicativos podem ser restritos a usar apenas um subconjunto das CPUs do sistema; afinal, é (geralmente) o computador do usuário / administrador e não o do programador.
Donal Fellows
2

O conselho de um thread por núcleo se aplica quando o objetivo é acelerar a execução paralela.

Um motivo completamente diferente e igualmente válido é a simplicidade do código quando ele precisa responder a eventos imprevisíveis. Portanto, se um programa precisar escutar em 100 soquetes e parecer dar toda a atenção a cada um, esse é o uso perfeito para o encadeamento. Outro exemplo é uma interface do usuário, na qual um thread lida com eventos da interface do usuário, enquanto outro realiza o processamento em segundo plano.

Mike Dunlavey
fonte
1
O processamento vinculado à IO pode ser feito como um encadeamento por fonte de eventos ou várias fontes de eventos podem ser multiplexadas em um único encadeamento. O código multiplexado é geralmente mais complexo e mais eficiente.
Donal Fellows
2

Você deseja um encadeamento para cada cálculo que possa prosseguir em taxas diferentes das de outros cálculos.

Para computação paralela ligada à CPU, que vem em grandes blocos de trabalho, geralmente você deseja um encadeamento por CPU, porque, quando todos estiverem ocupados, mais encadeamentos não ajudarão e apenas criarão sobrecarga no agendador. Se os blocos de trabalho tiverem tamanhos irregulares no tempo ou forem gerados dinamicamente em tempo de execução (geralmente acontece quando você tiver grandes estruturas de dados complexas para processar), convém anexar esses blocos a vários encadeamentos, para que um planejador sempre tenha um grande número de threads. defina para escolher quando algum bloco de trabalho for concluído, para manter todas as CPUs ocupadas.

Para o cálculo de ligação de E / S, geralmente você deseja um encadeamento para cada "canal" de E / S independente, pois eles se comunicam em taxas diferentes e os encadeamentos bloqueados no canal não impedem que outros encadeamentos progridam.

Ira Baxter
fonte
Esteja ciente de que esse estilo de encadeamento pode levar a alguns programas de arquitetura estranha. Eu vi um programa de 4 threads que tinha um thread para ler registros de uma tabela de banco de dados, um thread para gravar registros transformados em um soquete, um thread para ler as respostas para essas gravações de soquete (que voltaram com defeito) e assincronamente) e um encadeamento para modificar o registro do banco de dados original com a resposta. Condições de erro não intuitivas se seguiram.
precisa
Uma visão é que esse estilo produz programas ímpares. Outra visão é essa do estilo natural que os programas deveriam ter. Não sei sobre as condições de erro "não intuitivas"; se você tem muitas coisas acontecendo, e uma delas obtém um erro, garantir que seja propagada corretamente nos cálculos assíncronos é um problema para muitos idiomas [estupidamente, exceções Java não são definidas nos limites do encadeamento], mas não são um problema com o estilo do programa. (Nosso idioma de programação do PARLANSE [veja minha biografia] lida com exceções além dos limites do encadeamento, para que seja possível fazer isso corretamente.).
Ira Baxter
1

A regra geral para threads é que você deseja pelo menos um thread de trabalho "ativo" (capaz de executar seus comandos imediatamente após o tempo da CPU) para cada "unidade de execução" disponível no computador. Uma "unidade de execução" é um processador de instruções lógicas, portanto, um servidor Xeon com quatro núcleos e quatro núcleos com hyperthread teria 32 EUs (4 chips, 4 núcleos por chip, cada hyperthread). Seu Core i7 médio teria 8.

Um encadeamento por UE é o uso máximo da energia da CPU, desde que os encadeamentos estejam sempre em estado de execução; quase nunca é esse o caso, pois os threads precisam acessar a memória não armazenada em cache, o disco rígido, as portas de rede etc. que eles devem esperar e que não exigem atenção ativa da CPU para serem executados. Dessa forma, você pode aumentar ainda mais a eficiência geral com mais threads na fila e ansiosos para começar. Isso tem um custo; quando uma CPU alterna um encadeamento, deve armazenar em cache os registros, o ponteiro de execução e outras informações de estado normalmente mantidas no funcionamento mais interno de uma UE e acessadas muito rapidamente, permitindo que outras UEs nesse chip da CPU possam buscá-lo. Também requer threads no sistema operacional para decidir para qual thread deve ser alternado. Por fim, quando uma UE alterna threads, perde os ganhos de desempenho do pipelining usado pela maioria das arquiteturas de processador; ele precisa liberar o pipeline antes de alternar os threads. Mas, como tudo isso ainda leva muito menos tempo, em média, do que simplesmente esperar o disco rígido ou até a RAM voltar com informações, vale a pena o custo.

No entanto, em geral, quando você ultrapassa o dobro do número de threads "ativos" que os EUs, o sistema operacional começa a gastar mais dos threads de agendamento de tempo dos EUs e os EUs passam mais tempo alternando entre eles do que realmente são gastos executando threads ativos de programas. Este é o ponto das deseconomias de escala; na verdade, levará mais tempo para que um algoritmo multithread seja executado se você adicionar um thread extra nesse momento.

Portanto, no geral, você deseja manter pelo menos o número de threads em seu programa que possui UEs no computador, mas deseja evitar ter mais que o dobro do número que não está esperando ou dormindo.

KeithS
fonte
Se N é o número de threads e U o número de unidades, o OP questionou a regra "N = U". Você está relaxando com uma regra "U <= N <= 2 U". Eu iria um pouco mais longe e diria que "N <= c U" para uma constante "razoavelmente pequena" (conhecida pelo programador) c é aceitável (se os benchmarks mostrarem desempenho razoável). Eu ficaria muito preocupado se o número de threads pudesse aumentar para um número potencialmente ilimitado.
precisa saber é o seguinte
1

Você deve usar um thread para:

Cada processador que você precisa para se manter ocupado.

Cada E / S pode ser útil pendente simultaneamente e não é possível executar de maneira não-bloqueadora. (Por exemplo, lê de um disco local.)

Cada tarefa que requer um encadeamento dedicado, por exemplo, chamar uma biblioteca que não possui interface sem bloqueio ou onde as interfaces sem bloqueio não são apropriadas. Isso inclui tarefas como monitorar o relógio do sistema, disparar cronômetros e assim por diante.

Alguns extras para proteger contra bloqueios inesperados, como falhas de página.

Alguns extras para proteger contra o bloqueio esperado que não valem a pena otimizar, por exemplo, em códigos não críticos. (Por exemplo, se você raramente precisar fazer uma solicitação de DNS, provavelmente não vale a pena fazer solicitações de DNS de forma assíncrona. Basta criar alguns threads extras e facilitar sua vida.)

Se você seguir a regra "um thread por processador", todo o seu código será crítico para o desempenho. Qualquer código que bloqueie por algum motivo significa que seu processo não pode usar esse processador. Isso torna a programação muito mais difícil sem uma boa razão.

David Schwartz
fonte
0

Você pode gerar processos e encadeamentos para permitir a utilização de um sistema multicore \ multiprocessador para um único programa; nesse caso, você não obtém nenhum benefício (pelo menos para o único programa) de ter mais encadeamentos \ processos do que núcleos.

Ou você pode ter rotinas que pesquisam um evento que normalmente bloqueia a execução adicional. Em vez disso, amarre a CPU com a pesquisa. Em vez disso, você pode criar um encadeamento que ficará no estado inativo até que o evento apropriado o ative. Esse método é muito usado em servidores da Web e filas de eventos da GUI. A maioria dos programas deseja ter algum tipo de armazenamento central de dados (mesmo que seja o código de execução do programa) que todos os threads possam acessar, então acho que é por isso que eles usam threading nos processos.

Peter Smith
fonte
0

Os aplicativos que você menciona raramente executam todas essas dezenas de threads simultaneamente. A maioria deles apenas fica lá porque está em um pool de threads . O aplicativo envia várias tarefas para uma fila, que é eliminada por threads no pool de threads.

Por que a piscina é tão grande então? Porque, muitas vezes, os threads precisam aguardar outros recursos, como disco, rede, usuário, algum outro thread, etc. Enquanto um thread estiver aguardando, é apropriado executar outros threads para utilizar totalmente o processador. O dimensionamento adequado da piscina é complicado, no entanto. Poucos threads e você perderá o desempenho porque o processador não é totalmente utilizado enquanto espera por algo. Muitos threads e você perderá o desempenho devido à alternância entre eles.

Joonas Pulakka
fonte