Este artigo foi traduzido por máquina.

DEPURAÇÃO PARALELA

Depurando aplicativos paralelos com base em tarefas no Visual Studio 2010

Daniel Moth e Stephen Toub

Baixar o código de exemplo

Peça ao fabricante do hardware qualquer CPU ou GPU e eles informará que o futuro é vários núcleos. Velocidades de núcleo de processador não estão aumentando o exponencial taxas de há quatro décadas;em vez disso, novas máquinas estão sendo criadas com mais núcleos. Como resultado, o "gratuitos"aprimoramentos de desempenho que os desenvolvedores de aplicativos dependiam ano após ano são perdidos. Para recuperar o bônus oferecidos pelo hardware melhor e mais--e aprimorar seus aplicativos com novos recursos de desempenho sensíveis--você precisa tirar proveito de vários núcleos por meio do paralelismo.

No Visual C++ 10 e o Microsoft .NET Framework 4, tanto disponível com o Visual Studio 2010, Microsoft está apresentando novas bibliotecas e tempos de execução para facilitar significativamente o processo de Expressando paralelismo em seu código base, juntamente com a nova ferramenta de suporte para análise de desempenho e a depuração dos aplicativos em paralelo. Neste artigo, você aprenderá sobre o suporte à depuração no Visual Studio 2010, sendo que grande parte dele está concentrado em modelos de programação com base em tarefas.

A necessidade de programação baseada em tarefa

O motivo para injetar o paralelismo no seu aplicativo é tirar proveito de vários núcleos. Uma parte do trabalho única e seqüencial será executado no principal somente um por vez. Para um aplicativo usar vários núcleos, várias partes do trabalho são necessários para permitir que vários threads ao processo que funcionam em paralelo. Assim, dada uma única parte do trabalho, alcançar paralela aceleração por meio da execução de vários núcleos requer o particionamento que parte do trabalho único em várias unidades podem ser executados simultaneamente.

Os esquemas mais simples envolvem particionamento estático: Divida o trabalho em um número fixo de unidades de tamanho fixo. Claro, você não deseja tem que escrever seu código para cada configuração de hardware, que ele será executado e predetermining um número fixo de unidades antecipadamente Inibe a escalabilidade do seu aplicativo que seja executado em máquinas maior e melhores. Em vez disso, você pode escolher o número de unidades dinamicamente em tempo de execução com base nos detalhes da máquina. Por exemplo, você pode particionar o trabalho em uma unidade por núcleo. Dessa forma, se todas as unidades forem iguais no tamanho em termos de tempo de processamento necessitam, e se você usar um thread por unidade, deve ser capaz de saturem a máquina.

Essa abordagem, entretanto, ainda deixa muito a desejar. É raro que uma carga de trabalho real pode ser dividida de forma que cada unidade é garantida tenham a mesma quantidade de tempo, especialmente quando você levar em conta fatores externos, como outras cargas de trabalho que possam estar em execução na máquina simultaneamente e consumindo alguns dos recursos da máquina. Em tais casos, uma unidade por núcleo particionamento provavelmente acabar distribuindo o trabalho unevenly: alguns segmentos concluirá suas unidades antes de outros, resultando em desequilíbrio de carga, e alguns núcleos sentar ocioso enquanto outras pessoas concluir. Para resolver isso, você quer over-partition o trabalho, dividindo a carga de trabalho em unidades viáveis menores para que todos os recursos da máquina podem partake no processamento da carga de trabalho até que seja concluída.

Se executar uma unidade de trabalho contraídos zero sobrecarga, a solução proposta apenas seria ideal, mas poucas operações de mundo real incorrer em sobrecarga de zero. Historicamente, os threads foram o mecanismo para executar uma unidade de trabalho: criar um thread para cada unidade de trabalho, deixá-lo executar e, em seguida, subdividir o thread. Infelizmente, segmentos são relativamente pesado peso e a sobrecarga decorrente de usá-las dessa maneira pode proibir o tipo de over-partitioning que descrevemos. O que você precisa é um mecanismo leve para executar essas unidades particionadas para minimizar a sobrecarga--um mecanismo que permitirá que você over-partition com menos guilt. Com essa abordagem, em vez de criar um thread por unidade, você pode utilizar um agendador agenda unidades individuais a serem executados em threads que gerencia, mantendo o número de unidades o menor possível enquanto ainda garantindo taxa de transferência máxima.

O que nós acabei de descrever é um pool segmento, que amortizes o custo de gerenciamento de segmentos em todos os itens de trabalho agendados para ele, assim minimizar a sobrecarga associada com um item de trabalho individuais. No Windows, um pool de segmentos é acessível por meio a função QueueUserWorkItem exportada de Kernel32.dll. (Windows Vista introduziu novo segmento de pool de funcionalidade bem.) No .NET Framework 4, como um pool está acessível através da classe System.Threading.ThreadPool.

Enquanto as APIs mencionadas anteriormente habilitar Decomposição com sobrecarga mínima relativamente, eles está amplamente direcionados "disparar e esquecer"trabalho. Por exemplo, a classe ThreadPool do .NET Framework 4 não fornece qualquer mecanismo consistente para manipulação de exceção, para o cancelamento de trabalho, para aguardar no trabalho para concluir, para receber notificações quando trabalho for concluída e assim por diante. Esses intervalos serão preenchidos por novas APIs no .NET Framework 4 e Visual C++ 10 projetado para "com base em tarefa"Programando em código gerenciado e nativo. Tarefas representam unidades de trabalho que podem ser executadas com eficiência por um agendador subjacente ao expor ainda funcionalidade avançada para trabalhar com e controlar os aspectos de sua execução. No Visual C++ 10, essas APIs são centralizadas em tipos Concurrency::task_group e Concurrency::task_handle. No .NET Framework 4, eles são centralizados na nova classe System.Threading.Tasks.Task.


Figura 1 Janela pilhas Parallel


A Figura 2 Parallel janela tarefas

Depuração no Visual Studio hoje

O histórico de desenvolvimento de software demonstrou tempo tempo novamente que modelos de programação se beneficiar bastante exemplar suporte à depuração, e o Visual Studio 2010 oferece quanto a isso fornecendo duas novas depuração janelas de ferramentas para auxiliar na programação paralela com base em tarefas. Mas antes de examinarmos esses novos recursos, vamos rever a experiência de depuração no Visual Studio hoje para definir o estágio.

(Para o restante deste artigo, usaremos os tipos baseados em tarefas do .NET para fins de explicação, mas o suporte à depuração descrito aplica-se igualmente a código nativo bem.)

O ponto de entrada para depuração de um processo no Visual Studio é, claro, anexar o depurador. Isso ocorre por padrão quando você pressiona F5 (o equivalente a escolha do Debug >Iniciar a depuração de comando) em um projeto é aberto no Visual Studio. Você pode também manualmente anexar o depurador a um processo escolhendo o Debug >Anexe a processo comando de menu. Depois que o depurador é anexado, a próxima etapa é interromper o depurador. Isso pode ocorrer de várias maneiras, incluindo atingindo um ponto de interrupção definido pelo usuário, dividir manualmente (por meio do Debug >Interromper o comando All), o processo de solicitação (por exemplo, em código gerenciado, por meio de uma chamada para o método System.Diagnostics.Debugger.break), ou até mesmo quando uma exceção é lançada.

Figura 3 Localizando Primes

static void Main(string[] args)
{
var primes =
from n in Enumerable.Range(1,10000000)
.AsParallel()
.AsOrdered()
.WithMergeOptions(ParallelMergeOptions.NotBuffered)
where IsPrime(n)
select n;
foreach (var prime in primes) Console.Write(prime + “, ”);
}
public static bool IsPrime(int numberToTest) // WARNING: Buggy!
{
// 2 is a weird prime: it’s even. Test for it explicitly.
if (numberToTest == 2) return true;
// Anything that’s less than 2 or that’s even is not prime
if (numberToTest < 2 || (numberToTest & 1) == 0) return false;
// Test all odd numbers less than the sqrt of the target number.
// If the target is divisible by any of them, it’s not prime.
// We don’t test evens, because if the target is divisible
// by an even, the target is also even, which we already checked for.
int upperBound = (int)Math.Sqrt(numberToTest);
for (int i = 3; i < upperBound; i += 2)
{
if ((numberToTest % i) == 0) return false;
}
// It’s prime!
return true;
}

Após o processo de entra no depurador, todos os threads em seu aplicativo são interrompidos: Nenhum código é executado nesse ponto até que você continuar a execução (excluindo threads que usa o depurador próprio). Este interromper em execução permite que você inspecione o estado do seu aplicativo nesse momento. Ao inspecionar o estado do aplicativo, você geralmente tem uma imagem mental do qual o estado deve ser, e você pode usar várias janelas de depurador para localizar uma diferença entre expectativa e realidade.

As janelas de depuração principais que os desenvolvedores usar no Visual Studio são a janela de threads, janela Call Stack, e a janela de threads janelas variáveis (locais, Autos, inspeção) .o exibe uma lista de todos os threads em seu processo, incluindo informações como o identificador do segmento e prioridade de thread e uma indicação (uma seta amarela) do segmento atual, que, por padrão é o segmento que estava executando quando o depurador interrompeu no processo. Provavelmente as informações mais importantes sobre um segmento são onde estava executando quando o depurador interrompido sua execução, mostrada por coluna local no quadro de pilha de chamadas. Focalizar o cursor essa coluna revela a pilha de chamadas igualmente importante--a série ou o método chama que o segmento estava no processo de execução antes de alcançar o local atual.

A janela Call Stack, que exibe a pilha de chamadas do thread atual, fornece muito mais informações sobre a pilha de chamada, incluindo oportunidades de interação.

Para exibir a pilha de chamada de outro segmento na janela Call Stack, você precisará fazer outro thread atual clicando duas vezes na janela threads. O método está sendo executado no momento (que é o topo da pilha de chamada) é indicada por uma seta amarela e é conhecido como "quadro superior",quadro de folha""ou quadro de pilha ativo"." Este é o método do qual o thread irá continuar a execução quando você deixar o depurador e continuar a executar o aplicativo. Por padrão, o quadro de pilha ativa é também o quadro de pilha atual--em outras palavras, o método unidades a inspeção de variável, que descreveremos a seguir.


Figura 4 Definindo pontos de interrupção condicional

As variable windows são usadas para inspecionar os valores de variáveis em seu aplicativo. As variáveis de métodos locais geralmente são procuradas em windows Locals e Autos;estado global (variáveis não declarados em um método) pode ser examinado, adicionando-a janela Watch. Começando com Visual Studio 2005, mais e mais desenvolvedores examinar o estado focalizando seus ponteiros do mouse uma variável de interesse e revisar o resultante DataTip pop-up (que pode ser pensado como um atalho para as janelas de inspeção de variáveis rápida). É importante observar que os valores para variáveis podem ser exibidos somente se as variáveis estão no escopo do quadro de pilha atual (que, como nós estabelecida anteriormente, é por padrão o quadro ativo pilha do thread atual).

Para examinar variáveis que estavam no escopo anteriormente na pilha de chamada do thread, você precisará alterar o quadro de pilha atual clicando duas vezes o quadro de pilha que você deseja examinar na janela Call Stack. Neste ponto, o novo quadro de pilha atual é indicado por uma seta verde cauda curvo (enquanto o quadro de pilha ativo mantém a seta amarela). Se você também desejar examinar variáveis em outro thread, você precisará alterar o thread atual na janela threads e, em seguida, alternar o quadro atual de sua pilha de chamadas na janela Call Stack.

Em resumo, quando você dividir o processo no depurador, muito facilmente você pode inspecionar as variáveis no escopo do método de um dos threads em execução. No entanto, para criar uma imagem completa de onde todos os seus segmentos estão em execução, você precisa examinar a pilha de chamadas de cada thread individualmente clicando duas vezes em cada thread para torná-lo atual, observando a janela Call Stack e criar a imagem holística mentalmente. Além disso, para examinar variáveis em vários quadros de pilha de vários segmentos, dois níveis de indireção são necessários novamente: Alternar threads e, em seguida, alterne quadros.

Pilhas paralelas

Quando aplicativos usam mais threads (que se tornará comuns como pessoas usam máquinas com mais recursos de processamento), você precisa ser capaz de ver em um único modo de exibição onde esses segmentos estão executando qualquer determinado momento. Esse é o que a janela de ferramenta pilhas paralela no Visual Studio 2010 oferece.

Para preservar espaço real de tela, mas também para indicar os métodos de interesse particular para cenários de paralelismo, a janela coalesces no mesmo nó os segmentos de pilha de chamada que são comuns entre threads em sua raiz. Por exemplo, na Figura 1, você pode ver as pilhas de chamada de três segmentos em um único modo de exibição. A figura mostra um segmento que ficou do principal para A para B e dois outros threads que iniciado a partir do mesmo código externo e, em seguida, passou para a. Um desses threads de continuação para B e em seguida, para alguns códigos externos e o outro thread continuação para C e em seguida, para alguns AnonymousMethod. AnonymousMethod também é o quadro de pilha ativo, e ele pertence ao thread atual. Muitos outros recursos são suportados nessa janela, como o zoom, um modo de exibição bird's-eye, filtragem de threads por meio de sinalização e a maioria das mesmas funcionalidades já disponível na janela Call Stack.

Choosing the Freeze All Threads But This Command
Figura 5 Escolhendo o congelamento todos os threads mas este comando

Coalescing of Stack Frames
Figura 6 Concentração de quadros de pilha

Se seu aplicativo cria tarefas em vez de segmentos, você pode alternar para uma visão centrada em tarefa. Neste modo de exibição, pilhas de chamadas de segmentos executando tarefas não são omitidas. Além disso, pilhas de chamadas de segmentos são cortadas para representar as pilhas de chamada real das tarefas--ou seja, uma pilha de chamada de thread único poderia incluir duas ou três tarefas que você deseja dividir check-out e exibir separadamente. Um recurso especial da janela pilhas Parallel permite que você o diagrama em um único método de tabela dinâmica e observe claramente os chamadores e callees de contexto que método.

Figura 7 Código baseado em tarefas com dependências

static void Main(string[] args) // WARNING: Buggy!
{
var task1a = Task.Factory.StartNew(Step1a);
var task1b = Task.Factory.StartNew(Step1b);
var task1c = Task.Factory.StartNew(Step1c);
Task.WaitAll(task1a, task1b, task1c);
var task2a = Task.Factory.StartNew(Step2a);
var task2b = Task.Factory.StartNew(Step2b);
var task2c = Task.Factory.StartNew(Step2c);
Task.WaitAll(task1a, task1b, task1c);
var task3a = Task.Factory.StartNew(Step3a);
var task3b = Task.Factory.StartNew(Step3b);
var task3c = Task.Factory.StartNew(Step3c);
Task.WaitAll(task3a, task3b, task3c);
}

Tarefas paralelas

Ao observar as pilhas de chamada real de tarefas na janela pilhas Parallel, outro novo depurador janela expõe informações adicionais sobre tarefas, incluindo a ID da tarefa, o thread atribuído à tarefa, o local atual e o ponto de entrada (delegar) passado para a tarefa de criação. Esta janela, a janela tarefas paralelas, chamada expõe recursos similares para a janela threads, como indicando a tarefa atual (a tarefa de nível mais alto em execução no thread atual), a capacidade de alternar a tarefa atual, sinalizando de tarefas e congelar e descongelar threads.


Figura 8 Usando Parallel tarefas para localizar problemas de dependência

Talvez o valor maior para os desenvolvedores é a coluna Status. As informações fornecidas na coluna Status permite que você diferencie executando tarefas e tarefas que está aguardando (em outra tarefa ou em uma primitiva de sincronização) ou tarefas que são travado (uma especialização de tarefas de espera para o qual a ferramenta detectar uma cadeia de espera circular). A janela tarefas paralelas também exibe as tarefas agendadas, que são tarefas que ainda não tiver executado ainda, mas estão colocada em alguns fila aguardando para ser executado por um thread. Um exemplo pode ser visto na Figura 2. Para obter mais informações em janelas de pilhas Parallel e tarefas paralelas, consulte que o blog postagens danielmoth.com/Blog/labels/ParallelComputing.html e a documentação do MSDN msdn.microsoft.com/dd554943(VS.100).aspx.

Figura 9 Travado código

static void Main(string[] args)
{
int transfersCompleted = 0;
Watchdog.BreakIfRepeats(() => transfersCompleted, 500);
BankAccount a = new BankAccount { Balance = 1000 };
BankAccount b = new BankAccount { Balance = 1000 };
while (true)
{
Parallel.Invoke(
() => Transfer(a, b, 100),
() => Transfer(b, a, 100));
transfersCompleted += 2;
}
}
class BankAccount { public int Balance; }
static void Transfer(BankAccount one, BankAccount two, int amount)
{
lock (one) // WARNING: Buggy!
{
lock (two)
{
one.Balance -= amount;
two.Balance += amount;
}
}
}

Localizar o bug

Uma das melhores maneiras de compreender a nova funcionalidade de ferramentas é vê-la em ação. Para fazer isso, criamos um alguns trechos de código com bugs e usaremos novas janelas de ferramenta para localizar os erros subjacentes no código.

Nível único

Primeiro, considere o código mostrado na Figura 3. O objetivo desse código é para os números primos entre 1 e 10,000,000 de saída e para fazer isso em paralelo. (O paralelização suporte é fornecido pelo LINQ paralelo;consulte blogs.msdn.com/pfxteam e msdn.microsoft.com/dd460688 (VS.100) .aspx para obter mais informações.) A implementação de IsPrime é buggy, como você pode ver a execução do código e exibindo a primeira saída números alguns:

2, 3, 5, 7, 9, 11, 13, 15, 17, 19, 23, 25, ...

A maioria desses números são primes mas 9, 15 e 25 não são. Se isso fosse um aplicativo single-threaded, você pode percorrer facilmente o código para localizar o motivo para resultados imprecisos. No entanto, quando executar a depuração de passo único (escolha Debug >Etapa para, por exemplo) em um programa multithread, qualquer thread no programa está qualificado para depuração. Isso significa que você etapa você pode ser saltar entre threads, tornando mais difícil entender o fluxo de controle e informações de diagnóstico sobre o local atual no programa. Para ajudar com isso, você pode aproveitar vários recursos do depurador. A primeira é definir pontos de interrupção condicionais.

Como mostrado na Figura 4, você pode definir um ponto de interrupção (no caso na primeira linha do método IsPrime) e indicar ao depurador para quebrar em somente quando uma determinada condição é atendida--nesse caso, quando um de imprecisos "primes"está sendo avaliada.

Poderíamos ter definido o depurador para quebrar quando um desses valores foi atingido (em vez de quando qualquer uma delas sofreu hit), mas não é possível fazer suposições sobre a ordem em que o PLINQ avalia os valores nos bastidores. Em vez disso, nós dissemos que o depurador para procurar por qualquer um desses valores para minimizar o tempo de espera antes de ele quebras.

Depois que o depurador quebra, queremos dizer a ele para a etapa única apenas o segmento atual. Para fazer isso, nós podem tirar proveito de capacidade do depurador para congelar e descongelar threads e especificar que congelada threads não executado até que nós descongelá-los. A nova janela tarefas paralelas torna mais fácil localizar o thread que deve ser permitido para continuar (procure o ícone de seta amarela) e para congelar todos os outros segmentos (via ContextMenu), conforme mostrado no Figura 5.

Com os irrelevantes segmentos congelados, podemos agora única etapa por meio de nosso IsPrime bugs. Por depuração numberToTest == 25, podemos facilmente ver o que é deixado errado: o loop deve incluir o valor upperBound no seu teste, enquanto esse valor é atualmente sendo excluído porque o loop usa o menor - que operador em vez de menos-de-ou-igual. Aqui, a raiz quadrada de 25 é 5, e 25 é igualmente divisível por 5, mas 5 não ser testada para 25 é categorizado erroneamente como um "Prime".

A janela de pilhas paralela também fornece uma exibição interessante, consolidada do que está acontecendo em nosso programa quando nós quebra. Figura 6 mostra o estado atual do aplicativo depois que executá-lo novamente e neste momento quebrar explicitamente no uso quebra todos os recursos do depurador.

PLINQ é executando IsPrime em várias tarefas e o valor de numberToTest para todas as essas tarefas é visível no pop-up, mostrando aqui que a tarefa 1 está processando numberToTest == 8431901, enquanto a tarefa 2 está processando numberToTest == 8431607.

Problemas de dependência

O código na Figura 7 mostra uma instância de um padrão comum em aplicativos paralelos. Este código bifurcações desativado várias operações (step1a step1b, step1c, que são todos os métodos do formulário "void StepXx()") que pode ser executado em paralelo e, em seguida, ingressar neles. Conseqüentemente, as bifurcações de aplicativo novamente com o código que requer as operações anteriores já ser concluída devido a uma dependência sobre as operaçõesefeitos colaterais (como gravar dados para alguns matrizes compartilhadas).

Infelizmente, esse código inclui um bug e o desenvolvedor que escreveu está vendo alguns resultados imprecisos sendo computados pelo terceiro conjunto de tarefas. A implicação é que, mesmo que o desenvolvedor está esperando por todas as tarefas anteriores para concluir, algo é amiss e, na verdade, nem todas as computações anteriores concluiu seus resultados. Para depurar o código, o desenvolvedor define um ponto de interrupção na última chamada WaitAll e usa a janela tarefas paralelas para ver o estado atual do programa, que é mostrado na Figura 8.

Se suficiente, a janela de tarefas paralelas mostra que a tarefa para Step2c ainda está sendo mesmo que as tarefas para a etapa 3 foram agendadas. Uma revisão da segunda chamada Task.WaitAll demonstra motivo: devido a erros de digitação, task1a task1b e task1c estão sendo aguardados em vez de suas contrapartes task2.

Deadlocks

Figura 9 fornece o prototypical exemplo de um cenário de deadlock, que resulta da não prestar atenção para bloquear a ordenação. O código principal é continuamente transferência de dinheiro entre contas bancárias. O método de transferência deve ser thread-safe, para que ele pode ser chamado simultaneamente de vários threads. Como tal, ele internamente bloqueia em BankAccount objetos entregue a ele simplesmente ao bloqueio no primeiro e, em seguida, o bloqueio na segunda. Infelizmente, esse comportamento pode levar a deadlocks, como executar que esse código irá demonstrar. Finalmente, o depurador quebras em quando ele encontra que não as transferências são prosseguir. (Quebra é executada usando código emite um Debugger.Break se ele percebe que não transferências novas foram concluídas depois de um determinado período de tempo. Esse código é incluído no download que acompanha este artigo.)

Information on Deadlocks in Parallel Tasks
Figura 10 Informações sobre deadlocks em tarefas paralelas

 

Parallel Stacks Showing Deadlocks
Figura 11 Paralelo pilhas Mostrar Deadlocks

 

Method View in Parallel Stacks
Figura 12 Método exibir no paralelo pilhas

Quando você está trabalhando no depurador, você imediatamente ver uma representação gráfica demonstrar que há um deadlock, conforme mostrado no Figura 10. A figura também demonstra que focalizar o ponteiro status travado espera fornece mais detalhes sobre como exatamente o que está sendo aguardado e qual thread está mantendo o recurso protegido. Observando a coluna de atribuição de thread, você pode ver que 2 está aguardando um recurso mantido por tarefa 1 e se você focalizar a tarefa 1, você veria o inverso.

Essas informações também podem ser deduzidas da janela de ferramenta pilhas paralela. Figura 11 mostra a exibição de tarefa em paralelo pilhas, que destaca o que há duas tarefas, cada um deles está bloqueada em uma chamada para Monitor.ENTER (devido às instruções de bloqueio da Figura 9). E Figura 12 demonstra a exibição de método disponíveis na janela pilhas paralela (através do botão da barra de ferramentas correspondente). Concentrando nossa visão sobre o método de transferência, podemos facilmente ver que há duas tarefas na transferência, que passaram uma chamada para Monitor.ENTER. Focalizar o ponteiro nessa caixa fornece mais informações sobre status deadlocked de ambas as tarefas.

Figura 13 Criando um bloqueio Convoy

static void Main(string[] args) // WARNING: Buggy!
{
object obj = new object();
Enumerable.Range(1, 10).Select(i =>
{
var t = new Thread(() =>
{
while (true)
{
DoWork();
lock (obj) DoProtectedWork();
}
}) { Name = “Demo “ + i };
t.Start();
return t;
}).ToList().ForEach(t => t.Join());
}

 

Comboios de bloqueio

Comboios de bloqueio podem ocorrer quando vários segmentos competem repetidamente para a mesma região protegida.(Wikipedia fornece um resumo boa de comboios de bloqueio em de en.wikipedia.org/wiki/Lock_convoy).O código no Figura 13 fornece um exemplo melhor de um comboio de bloqueio: vários segmentos repetidamente fazendo algumas quantidade de trabalho fora de uma área protegida, mas, em seguida, fazer um bloqueio para algum trabalho adicional dentro que protegido por região.Dependendo da proporção entre o trabalho dentro e fora da região, problemas de desempenho podem resultar.Esses tipos de problemas são visíveis após a execução do programa usando uma ferramenta como o profiler de simultaneidade que está disponível no Visual Studio 2010, mas eles podem também ser detectados durante a execução usando uma ferramenta de depuração como a janela pilhas Parallel.

Lock Convoys with Parallel Stacks

Figura 14 Comboios de bloqueio com paralelo pilhas

Figura 14 mostra uma execução do código em Figura 13.O código foi dividido em poucos segundos para sua execução.Você pode ver na parte superior da imagem que nove threads atualmente bloqueadas aguardando um monitor--todos os threads aguardando um segmento sair DoProtectedWork para que um deles possa continuar em área protegida.

Resumindo

Neste artigo, você viu exemplos de como janelas de ferramenta do depurador do Visual Studio 2010 podem simplificar o ato de encontrar bugs no código baseado em tarefa.As APIs com base em tarefas para código gerenciado e nativo são mais sofisticadas do que o que nós poderia mostrar nos exemplos curtos neste artigo e incentivamos você a explorá-las ainda mais no .NET Framework 4 e no Visual C++ 10.Na frente ferramentas, bem como as duas janelas depurador novo discutido, um novo analisador de desempenho paralelo é integrado ao gerador de perfil existente no Visual Studio.

Para obter suas mãos em todos os bits acima, baixe a versão beta do Visual Studio 2010 a partir de msdn.microsoft.com/dd582936.aspx.

Daniel Moth *funciona na Parallel Computing Platform equipe da Microsoft.Ele pode ser contatado pelo seu blog em de danielmoth.com/Blog.
*Stephen Toub funciona em Parallel Computing Platform equipe na Microsoft. Ele também é editor-colaborador da MSDN Magazine.