Quantas threads são demais?

312

Estou escrevendo um servidor e envio cada ação para um thread separado quando a solicitação é recebida. Eu faço isso porque quase toda solicitação faz uma consulta ao banco de dados. Estou usando uma biblioteca de threads para reduzir a construção / destruição de threads.

Minha pergunta é: qual é um bom ponto de corte para encadeamentos de E / S como esses? Eu sei que seria apenas uma estimativa aproximada, mas estamos falando de centenas? Milhares?

Como eu iria descobrir qual seria esse ponto de corte?


EDITAR:

Obrigado a todos por suas respostas, parece que vou precisar testá-lo para descobrir meu limite de contagem de threads. A questão é: como sei que atingi esse teto? O que exatamente devo medir?

ryeguy
fonte
1
@ryeguy: O ponto principal aqui é que você não deve definir um máximo no pool de threads se não houver problemas de desempenho para começar. A maioria dos conselhos sobre como limitar um pool de threads a ~ 100 threads é ridícula, a maioria dos pools de threads tem / way / mais threads do que isso e nunca apresenta problemas.
GEOCHET 27/01/09
ryeguy, veja a adição à minha resposta abaixo sobre o que medir.
21420
Não esqueça que o Python é por natureza, não é realmente compatível com vários threads. A qualquer momento, um único código de código de bytecode está sendo executado. Isso ocorre porque o Python emprega o Global Interpreter Lock.
282
1
@ Jay D: Eu diria que no momento em que você atinge o teto é quando o seu desempenho começa a cair.
Ninjalj
6
@GEOCHET "O ponto principal aqui é que você não deve definir o máximo no pool de threads " Ummm ... diga o que? Os conjuntos de encadeamentos de tamanho fixo têm os benefícios de degradação e escalabilidade graciosas. Por exemplo, em uma configuração de rede, se você está gerando novos threads com base nas conexões do cliente, sem um tamanho fixo de pool, corre o risco real de aprender ( da maneira mais difícil ) quantos threads o servidor pode lidar e cada cliente conectado sofrerá. Um pool de tamanho fixo age como uma válvula de tubo, impedindo que o servidor tente morder mais do que pode mastigar.
b1nary.atr0phy

Respostas:

206

Algumas pessoas diriam que dois tópicos são muitos - eu não estou nesse campo :-)

Aqui está o meu conselho: meça, não adivinhe. Uma sugestão é torná-lo configurável e, inicialmente, defini-lo como 100, depois libere seu software para o estado selvagem e monitore o que acontece.

Se o uso de seu segmento atingir um pico de 3, 100 será demais. Se permanecer em 100 a maior parte do dia, aumente para 200 e veja o que acontece.

Você pode realmente ter seu próprio código monitorando o uso e ajustando a configuração para a próxima vez que iniciar, mas isso provavelmente é um exagero.


Para esclarecimentos e elaboração:

Eu não estou defendendo a rolagem do seu próprio subsistema de pool de threads, por qualquer meio use o que você possui. Mas, como você estava perguntando sobre um bom ponto de corte para encadeamentos, presumo que a implementação do conjunto de encadeamentos tenha a capacidade de limitar o número máximo de encadeamentos criados (o que é uma coisa boa).

Eu escrevi código de pool de threads e de conexão com o banco de dados e eles têm os seguintes recursos (que eu acredito que são essenciais para o desempenho):

  • um número mínimo de encadeamentos ativos.
  • um número máximo de threads.
  • desligando threads que não são usados ​​há um tempo.

O primeiro define uma linha de base para o desempenho mínimo em termos do cliente do conjunto de encadeamentos (esse número de encadeamentos está sempre disponível para uso). O segundo define uma restrição ao uso de recursos por encadeamentos ativos. O terceiro retorna à linha de base em tempos de silêncio, para minimizar o uso de recursos.

Você precisa equilibrar o uso de recursos de ter threads não utilizados (A) com o uso de recursos de não ter threads suficientes para fazer o trabalho (B).

(A) geralmente é uso de memória (pilhas e assim por diante), uma vez que um encadeamento que não funciona não estará usando muito da CPU. (B) geralmente será um atraso no processamento de solicitações à medida que elas chegarem, conforme for necessário aguardar a disponibilidade de um encadeamento.

É por isso que você mede. Como você afirma, a grande maioria dos seus threads estará aguardando uma resposta do banco de dados para que eles não estejam em execução. Existem dois fatores que afetam quantos threads você deve permitir.

O primeiro é o número de conexões de banco de dados disponíveis. Esse pode ser um limite rígido, a menos que você possa aumentá-lo no DBMS - presumo que seu DBMS possa ter um número ilimitado de conexões nesse caso (embora você deva medir isso também).

Então, o número de threads que você deve ter depende do seu uso histórico. O mínimo que você deve executar é o número mínimo que você já executou + A%, com um mínimo absoluto de (por exemplo, e configurá-lo como A) 5.

O número máximo de threads deve ser o seu máximo histórico + B%.

Você também deve monitorar as mudanças de comportamento. Se, por algum motivo, seu uso atingir 100% da disponibilidade por um tempo significativo (para afetar o desempenho dos clientes), você deverá aumentar o máximo permitido até que seja novamente B% ​​mais alto.


Em resposta ao "o que exatamente devo medir?" questão:

O que você deve medir especificamente é a quantidade máxima de encadeamentos em uso simultâneo (por exemplo, aguardando um retorno da chamada do DB) sob carga. Em seguida, adicione um fator de segurança de 10%, por exemplo (enfatizado, pois outros pôsteres parecem usar meus exemplos como recomendações fixas).

Além disso, isso deve ser feito no ambiente de produção para ajuste. Não há problema em obter uma estimativa antecipadamente, mas você nunca sabe qual produção será o seu caminho (e é por isso que todas essas coisas devem ser configuráveis ​​em tempo de execução). Isso é para capturar uma situação como duplicação inesperada das chamadas de clientes que chegam.

paxdiablo
fonte
Se os threads forem gerados nas solicitações recebidas, o uso da thread espelhará o número de solicitações não atendidas. Não há como determinar o número "ideal" disso. De fato, você encontrará mais threads que causam mais contenção de recursos e, portanto, o número de threads ativos aumentará.
Andrew Grant
@ Andrew, a criação de threads leva tempo e você pode determinar o número ideal com base em dados históricos [+ N%] (portanto, meça, não pense). Além disso, mais threads apenas causam contenção de recursos quando estão trabalhando, sem aguardar um sinal / semáforo.
paxdiablo
Onde esses dados na 'criação de encadeamentos' estão causando um problema de desempenho ao usar um conjunto de encadeamentos? Um bom conjunto de encadeamentos não estaria criando e destruindo encadeamentos entre tarefas.
GEOCHET 27/01/09
@Pax Se todos os seus threads estão aguardando os mesmos semáforos para executar consultas de banco de dados, essa é a própria definição de contenção. Também não é verdade dizer que os threads não custam nada se estiverem aguardando um semáforo.
Andrew Grant
1
@ Andrew, não consigo ver por que você bloqueará as consultas do banco de dados semáforo, qualquer banco de dados decente permitirá acesso simultâneo, com muitos threads aguardando as respostas. E os encadeamentos não devem custar tempo de execução enquanto estiverem bloqueados pelo semáforo, devem permanecer na fila bloqueada até que o semáforo seja liberado.
paxdiablo
36

Esta questão foi discutida minuciosamente e não tive a chance de ler todas as respostas. Mas aqui estão algumas coisas a serem levadas em consideração ao examinar o limite superior do número de encadeamentos simultâneos que podem coexistir pacificamente em um determinado sistema.

  1. Tamanho da pilha de encadeamentos: no Linux, o tamanho padrão da pilha de encadeamentos é 8 MB (você pode usar ulimit -a para descobrir).
  2. Memória virtual máxima suportada por uma determinada variante do SO. O Linux Kernel 2.4 suporta um espaço de endereço de memória de 2 GB. com o Kernel 2.6, eu um pouco maior (3GB)
  3. [1] mostra os cálculos para o número máximo de threads por VM máxima suportada. Para o 2.4, verifica-se que são cerca de 255 threads. para 2.6 o número é um pouco maior.
  4. Que tipo de agendador de kernel você tem. Comparando o planejador do kernel Linux 2.4 com o 2.6, o último fornece um planejamento O (1) sem dependência do número de tarefas existentes em um sistema, enquanto o primeiro é mais um O (n). Também os recursos SMP da programação do kernel também desempenham um bom papel no número máximo de threads sustentáveis ​​em um sistema.

Agora você pode ajustar o tamanho da pilha para incorporar mais threads, mas é necessário levar em consideração as despesas gerais do gerenciamento de threads (criação / destruição e agendamento). Você pode impor a afinidade da CPU a um determinado processo e a um determinado encadeamento para vinculá-los a CPUs específicas, a fim de evitar sobrecargas de migração de encadeamento entre as CPUs e evitar problemas de dinheiro a frio.

Observe que é possível criar milhares de threads a seu gosto, mas quando o Linux fica sem VM, ele aleatoriamente começa a matar processos (portanto, threads). Isso evita que o perfil do utilitário seja atingido no máximo. (A função de utilitário informa sobre o utilitário em todo o sistema para uma determinada quantidade de recursos. Com recursos constantes, neste caso, Ciclos e memória da CPU, a curva do utilitário se achata com um número cada vez maior de tarefas).

Estou certo de que o agendador do kernel do Windows também faz algo desse tipo para lidar com a utilização excessiva dos recursos

[1] http://adywicaksono.wordpress.com/2007/07/10/i-can-not-create-more-than-255-threads-on-linux-what-is-the-solutions/

Jay D
fonte
17

Se seus threads estiverem realizando algum tipo de trabalho com muitos recursos (CPU / Disco), raramente você verá benefícios além de um ou dois, e muitos prejudicarão o desempenho muito rapidamente.

A melhor opção é que seus threads posteriores parem enquanto os primeiros forem concluídos, ou alguns terão bloqueios de sobrecarga baixa em recursos com baixa contenção. Na pior das hipóteses, você começa a debulhar o cache / disco / rede e sua taxa de transferência geral cai pelo chão.

Uma boa solução é colocar solicitações em um pool que são despachadas para threads de trabalho de um pool de threads (e sim, evitar a criação / destruição contínua de threads é uma excelente primeira etapa).

O número de encadeamentos ativos nesse pool pode ser ajustado e escalonado com base nas descobertas de sua criação de perfil, no hardware em que você está executando e em outras coisas que podem estar ocorrendo na máquina.

Andrew Grant
fonte
Sim, e deve ser usado em conjunto com uma fila ou conjunto de solicitações.
Andrew Grant
2
@ Andrew: Por quê? Ele deve adicionar uma tarefa ao conjunto de encadeamentos sempre que receber uma solicitação. Cabe ao pool de encadeamentos alocar um encadeamento para a tarefa quando houver um disponível.
GEOCHET 27/01/09
Então, o que você faz quando recebe centenas de solicitações e sai de threads? Criar mais? Quadra? Retornar um erro? Coloque suas solicitações em um pool que possa ser tão grande quanto necessário e, em seguida, alimente essas solicitações na fila para o pool de threads, à medida que os threads ficarem livres.
Andrew Grant
"vários encadeamentos são criados para executar várias tarefas, geralmente organizadas em uma fila. Normalmente, há muito mais tarefas que encadeamentos. Assim que um encadeamento concluir sua tarefa, ele solicitará a próxima tarefa da fila até que todas as tarefas tenham sido concluídas. "
GEOCHET 27/01/09
@ Andrew: Não tenho certeza de qual pool de threads python o OP está usando, mas se você quiser um exemplo real dessa funcionalidade, estou descrevendo: msdn.microsoft.com/en-us/library/…
GEOCHET
10

Uma coisa que você deve ter em mente é que o python (pelo menos a versão baseada em C) usa o que é chamado de bloqueio global de intérpretes que pode ter um enorme impacto no desempenho em máquinas com vários núcleos.

Se você realmente precisa do máximo de python multithread, considere usar o Jython ou algo assim.

Chad Okere
fonte
4
Depois de ler isso, tentei executar a peneira das tarefas de Eratóstenes em três threads. Com certeza, na verdade, era 50% mais lento do que executar as mesmas tarefas em um único encadeamento. Obrigado pela atenção. Eu estava executando o Eclipse Pydev em uma máquina virtual que recebeu duas CPUs. A seguir, tentarei um cenário que envolva algumas chamadas ao banco de dados.
Don Kirkby
3
Existem dois (pelo menos) tipos de tarefas: limite da CPU (por exemplo, processamento de imagem) e limite de E / S (por exemplo, download da rede). Obviamente, o "problema" do GIL não afetará muito as tarefas vinculadas de E / S. Se suas tarefas estiverem ligadas à CPU, considere o multiprocessamento em vez do multithreading.
Iutinvg 15/06
1
sim, linha python têm melhorar se você tem muita rede io.I mudança lo de thread e tem 10 * mais rápido do que o código comum ...
tyan
8

Como Pax disse com razão, meça, não adivinhe . Que o que fiz para a testemunha de DNS e os resultados foi surpreendente: o número ideal de threads foi muito maior do que eu pensava, algo como 15.000 threads para obter os resultados mais rápidos.

Claro, isso depende de muitas coisas, é por isso que você deve se medir.

Medidas completas (somente em francês) no Combien de fils d'exécution? .

bortzmeyer
fonte
1
15.000? Isso é um pouco maior do que eu esperava. Ainda assim, se é isso que você tem, então é isso que você tem, não posso discutir com isso.
21420
2
Para esse aplicativo específico, a maioria dos threads aguarda uma resposta do servidor DNS. Portanto, quanto mais paralelismo, melhor, no tempo do relógio de parede.
Bortzmeyer
18
Eu acho que se você tiver 15000 threads que estão bloqueando algumas E / S externas, uma solução melhor seria massivamente menos threads, mas com um modelo assíncrono. Eu falo por experiência própria aqui.
26412 Steve
5

Eu escrevi vários aplicativos altamente multiencadeados. Geralmente, permito que o número de threads em potencial seja especificado por um arquivo de configuração. Quando eu ajustei para clientes específicos, defini o número alto o suficiente para que minha utilização de todos os núcleos da CPU fosse bastante alta, mas não tão alta que eu tenha problemas de memória (esses eram sistemas operacionais de 32 bits no Tempo).

Em outras palavras, quando você atinge algum gargalo, seja CPU, taxa de transferência de banco de dados, taxa de transferência de disco, etc., adicionar mais threads não aumentará o desempenho geral. Mas até você atingir esse ponto, adicione mais tópicos!

Observe que isso pressupõe que o (s) sistema (s) em questão sejam dedicados ao seu aplicativo e você não precisa jogar muito bem (evitar morrer de fome) outros aplicativos.

Matthew Lund
fonte
1
Você pode mencionar alguns dos números que você viu para a contagem de threads? Seria útil apenas ter uma noção disso. Obrigado.
Kovac
3

A resposta "big iron" geralmente é um encadeamento por recurso limitado - processador (limite da CPU), braço (limite de E / S) etc. -, mas isso só funciona se você puder rotear o trabalho para o segmento correto do recurso. ser acessado.

Onde isso não for possível, considere que você tem recursos fungíveis (CPUs) e recursos não fungíveis (armas). Para CPUs, não é essencial atribuir cada thread a uma CPU específica (embora isso ajude no gerenciamento de cache), mas para os braços, se você não pode atribuir um segmento ao braço, entra na teoria das filas e qual é o número ideal para manter os braços ocupado. Geralmente, estou pensando que, se você não puder rotear solicitações com base no braço usado, ter 2-3 segmentos por braço será quase certo.

Uma complicação ocorre quando a unidade de trabalho passada para o encadeamento não executa uma unidade de trabalho razoavelmente atômica. Por exemplo, você pode ter o encadeamento em um ponto acessando o disco, em outro momento aguardando na rede. Isso aumenta o número de "rachaduras" nas quais threads adicionais podem entrar e realizar um trabalho útil, mas também aumenta a oportunidade de threads adicionais poluirem os caches uns dos outros, etc., e atolar o sistema.

Obviamente, você deve pesar tudo isso contra o "peso" de um fio. Infelizmente, a maioria dos sistemas possui threads muito pesados ​​(e o que eles chamam de "threads leves" geralmente não são threads), por isso é melhor errar no lado inferior.

O que eu vi na prática é que diferenças muito sutis podem fazer uma enorme diferença na quantidade de threads ideal. Em particular, problemas de cache e conflitos de bloqueio podem limitar bastante a quantidade de simultaneidade prática.

Hot Licks
fonte
2

Uma coisa a considerar é quantos núcleos existem na máquina que executará o código. Isso representa um limite rígido para quantos threads podem continuar a qualquer momento. No entanto, se, como no seu caso, espera-se que os threads aguardem com freqüência por um banco de dados para executar uma consulta, você provavelmente desejará ajustá-los com base em quantas consultas simultâneas o banco de dados pode processar.

newdayrising
fonte
2
hum não. O ponto principal dos encadeamentos era (antes de vários processadores e vários processadores se tornarem predominantes) ser capaz de imitar o uso de vários processadores em uma máquina que possui apenas um. É assim que você obtém interfaces de usuário responsivas - um thread principal e threads auxiliares.
mmr 27/01/09
1
@mmr: Hum não. A idéia dos encadeamentos é permitir o bloqueio de E / S e outras tarefas.
GEOCHET 27/01/2009
4
A afirmação que fiz foi que o número de núcleos em uma máquina representa um limite rígido para o número de threads que podem estar funcionando em um determinado momento, o que é um fato. É claro que outros encadeamentos podem aguardar a conclusão das operações de E / S, e essa pergunta é uma consideração importante.
newdayrising
1
Enfim - você tem GIL em Python, o que torna os threads apenas teoricamente paralelos. Não é possível executar mais de 1 thread simultaneamente, portanto, são apenas as operações de resposta e bloqueio que importam.
Abgan
2
+1 Para realmente entender como os computadores funcionam. @ mmr: Você precisa entender a diferença entre parece ter vários processadores e vários processadores. @ Rich B: Um pool de threads é apenas uma das muitas maneiras de lidar com uma coleção de threads. É bom, mas certamente não é o único.
18749
2

Acho que isso é um pouco esquivo para sua pergunta, mas por que não colocá-los em processos? Meu entendimento sobre redes (desde os dias nebulosos de antigamente, eu realmente não codifico redes) é que cada conexão de entrada pode ser tratada como um processo separado, porque, se alguém faz algo desagradável em seu processo, isso não acontece. nuke o programa inteiro.

mmr
fonte
1
Para Python, isso é especialmente verdade, pois vários processos podem ser executados em paralelo, enquanto vários threads - não. O custo é, no entanto, bastante alto. Você precisa iniciar o novo interpretador Python a cada vez e conectar-se ao DB a cada processo (ou usar algum redirecionamento de pipes, mas também tem um preço).
Abgan
Alternar entre processos é - na maioria das vezes - mais caro do que alternar entre threads (alternar todo o contexto em vez de alguns registros). No final, depende muito da sua biblioteca de threading. Como as perguntas giravam em torno do encadeamento, presumo que os processos já estejam fora de questão.
317 Leonidas
Justo. Não sei ao certo por que é por isso que estou obtendo -2 pontos na pontuação, a menos que as pessoas realmente desejem ver respostas apenas de tópicos, em vez de incluir outras respostas que funcionem.
MMR
@mmr: Considerando que a pergunta era sobre / thread / pools, sim, acho que as pessoas deveriam estar esperando uma resposta sobre tópicos.
GEOCHET 27/01/09
A criação do processo pode ser feita uma vez na inicialização (ou seja, um pool de processos em vez de um pool de threads). Amortizado pela duração do aplicativo, isso pode ser pequeno. Eles não podem compartilhar informações facilmente, mas isso lhes dá a possibilidade de rodar em várias CPUs, então essa resposta é útil. +1.
paxdiablo 27/01
1

ryeguy, atualmente estou desenvolvendo um aplicativo semelhante e meu número de threads é definido como 15. Infelizmente, se eu aumentar para 20, ele trava. Então, sim, acho que a melhor maneira de lidar com isso é medir se sua configuração atual permite ou não mais ou menos que um número X de threads.

hiperboreia
fonte
5
A adição à sua contagem de threads não deve travar seu aplicativo aleatoriamente. Há alguma razão. Você faria bem em descobrir a causa, porque ela pode afetá-lo mesmo com menos threads em algumas circunstâncias, quem sabe.
Matthew Lund
-6

Na maioria dos casos, você deve permitir que o pool de threads lide com isso. Se você publicar algum código ou fornecer mais detalhes, poderá ser mais fácil verificar se há algum motivo para o comportamento padrão do pool de threads não ser o melhor.

Você pode encontrar mais informações sobre como isso deve funcionar aqui: http://en.wikipedia.org/wiki/Thread_pool_pattern

GEOCHET
fonte
1
@Pax: Esta não seria a primeira vez que a maioria das pessoas não queria responder à pergunta em questão (ou entendê-la). Eu não estou preocupado.
GEOCHET 27/01/09
-10

Tantos threads quanto os núcleos da CPU é o que ouvi muitas vezes.

masfenix
fonte
5
@ Rich, pelo menos, explique por que :-). Esta regra prática aplica-se apenas quando todos os threads são vinculados à CPU; eles recebem uma 'CPU' cada. Quando muitos dos threads são vinculados à E / S, geralmente é melhor ter muito mais threads do que 'CPU's (CPU é citado, pois se aplica a threads físicos de execução, por exemplo, núcleos).
21420
1
@ Abgan, eu não tinha certeza disso, pensando que talvez o Python criaria threads de sistema operacional "reais" (executados em várias CPUs). Se o que você diz é verdadeiro (não tenho motivos para duvidar), a quantidade de CPU não tem rolamentos - o encadeamento é útil apenas quando a maioria dos encadeamentos está aguardando algo (por exemplo, DB I / O).
paxdiablo 27/01
1
@ Rich: quando threading (real), a contagem de CPU tem influência, já que você pode executar vários threads não em espera de maneira realmente simultânea. Com uma CPU, apenas uma é executada e o benefício resulta de ter muitos outros encadeamentos aguardando um recurso que não seja da CPU.
paxdiablo 27/01
1
@Pax: Você não entende o conceito de pool de threads, então eu acho.
GEOCHET 27/01/09
1
@ Rich, eu entendo muito bem os pools de threads; parece que eu (e outros aqui) também entendemos o hardware melhor que você. Com uma CPU, apenas um encadeamento de execução pode ser executado, mesmo se houver outros aguardando por uma CPU. Duas CPUs, duas podem funcionar. Iff todos os segmentos estão aguardando para uma CPU, contagem da linha ideal é igual a ...
paxdiablo