Partilhar via


Padrões comuns para aplicativos multithreaded mal comportados

O Visualizador de Simultaneidade ajuda os desenvolvedores a visualizar o comportamento de um aplicativo multithreaded. Esta ferramenta inclui uma galeria de padrões comuns para aplicativos multithreaded que se comportam mal. A galeria inclui padrões visuais típicos e reconhecíveis que são expostos através da ferramenta, juntamente com uma explicação do comportamento que é representado por cada padrão, o resultado provável desse comportamento e a abordagem mais comum para resolvê-lo.

Contenção de bloqueio e execução serializada

Contenção de bloqueio resultando em execução serializada

Às vezes, um aplicativo paralelo teimosamente continua a ser executado em série, mesmo que tenha vários threads e o computador tenha um número suficiente de núcleos lógicos. O primeiro sintoma é o fraco desempenho multithreaded, talvez até um pouco mais lento do que uma implementação serial. No modo de exibição Threads, você não vê vários threads em execução em paralelo; em vez disso, você vê que apenas um thread está sendo executado a qualquer momento. Neste ponto, se clicar num segmento de sincronização num thread, pode ver uma stack de chamadas para o thread bloqueado (stack de chamadas de bloqueio) e o thread que removeu a condição de bloqueio (stack de chamadas de desbloqueio). Além disso, se a pilha de chamadas de desbloqueio ocorrer no processo que você está analisando, um conector de Thread-Ready será exibido. A partir deste ponto, podes navegar no teu código através das pilhas de chamadas que bloqueiam e desbloqueiam para investigar a causa da serialização de forma mais aprofundada.

Como mostrado na ilustração a seguir, o Visualizador de Simultaneidade também pode expor esse sintoma na Visualização de Utilização da CPU, onde, apesar da presença de vários threads, o aplicativo consome apenas um núcleo lógico.

Para obter mais informações, consulte a seção "Iniciar com o problema" no artigo da MSDN Magazine Thread Performance - Resource Contention Concurrency Profiling in Visual Studio.

Contenção de bloqueio

Distribuição desigual da carga de trabalho

Captura de tela de um gráfico de carga de trabalho para threads paralelos no Visualizador de Simultaneidade. Os fios terminam em momentos diferentes, mostrando um padrão de degrau de escada.

Quando uma distribuição irregular de trabalho ocorre em vários threads paralelos em um aplicativo, um padrão típico de degrau de escada aparece à medida que cada thread conclui seu trabalho, como mostrado na ilustração anterior. O Visualizador de Simultaneidade geralmente mostra horários de início muito próximos para cada thread simultâneo. No entanto, esses tópicos normalmente terminam de maneira irregular em vez de terminar simultaneamente. Esse padrão indica uma distribuição irregular do trabalho entre um grupo de threads paralelos, o que pode levar à diminuição do desempenho. A melhor abordagem para tal problema é reavaliar o algoritmo pelo qual o trabalho foi dividido entre os threads paralelos.

Conforme mostrado na ilustração a seguir, o Visualizador de Simultaneidade também pode expor esse sintoma na Visualização de Utilização da CPU como uma redução gradual na utilização da CPU.

Captura de tela da Visualização de Utilização da CPU no Visualizador de Simultaneidade mostrando um padrão de degrau de escada no final do gráfico de Utilização da CPU.

Excesso de subscrição

Captura de tela de um gráfico de carga de trabalho para todos os threads ativos no Visualizador de Simultaneidade. Uma legenda mostra a quantidade de tempo gasto em Execução e Preempção.

No caso de sobrescrição, o número de threads ativos em um processo é maior do que o número de núcleos lógicos disponíveis no sistema. A ilustração anterior mostra os resultados da sobresubscrição, com bandas de preempção significativas em todos os threads ativos. Além disso, a legenda mostra que uma grande porcentagem do tempo é gasto em Preempção (84% neste exemplo). Isso pode indicar que o processo está pedindo ao sistema para executar mais threads simultâneos do que o número de núcleos lógicos. No entanto, isso também pode indicar que outros processos no sistema estão usando recursos que foram assumidos como disponíveis para esse processo.

Você deve considerar o seguinte ao avaliar esse problema:

  • O sistema global pode estar sobrecarregado. Considere que outros processos no sistema podem estar a interromper os seus threads. Quando se pausa sobre um segmento de preempção na vista de threads, um tooltip identificará o thread e o processo que preemptou o mesmo. Este processo não é necessariamente aquele que foi executado enquanto o seu processo foi preemptado, mas fornece uma indicação sobre o que criou a pressão de preempção contra o seu processo.

  • Avalie como seu processo determina o número apropriado de threads para execução durante esta fase do trabalho. Se o seu processo calcular diretamente o número de threads paralelos ativos, considere modificar esse algoritmo para explicar melhor o número de núcleos lógicos disponíveis no sistema. Se você usar o Concurrency Runtime, a Task Parallel Library ou PLINQ, essas bibliotecas executarão o trabalho de calcular o número de threads.

E/S ineficiente

Inefficient_IO

O uso excessivo ou indevido de E/S é uma causa comum de ineficiências em aplicativos. Considere a ilustração anterior. O Perfil de Tempo Visível mostra que 44% do tempo de thread visível é consumido por Entrada/Saída. A linha do tempo mostra grandes quantidades de E/S, o que indica que a aplicação analisada é frequentemente bloqueada por E/S. Para ver detalhes sobre os tipos de E/S e onde o programa está bloqueado, aumente o zoom em regiões problemáticas, examine o Perfil da Linha do Tempo Visível e clique em um bloco de E/S específico para ver as pilhas de chamadas atuais.

Comboios de bloqueio

Comboios de bloqueio

Os comboios de bloqueio ocorrem quando o aplicativo adquire fechaduras por ordem de chegada e quando a taxa de chegada na eclusa é maior do que a taxa de aquisição. A combinação dessas duas condições faz com que as solicitações do bloqueio comecem a acumular-se. Uma maneira de combater esse problema é usar bloqueios "injustos", ou bloqueios que dão acesso ao primeiro thread para encontrá-los em estados desbloqueados. A ilustração anterior mostra esse comportamento do comboio. Para resolver o problema, tente reduzir a contenção dos objetos de sincronização e tente usar bloqueios não equitativos.

Visualização de tópicos