Compartilhar via


As práticas recomendadas de paralela da biblioteca

Este documento melhor descreve como fazer uso eficiente de modelos paralela a biblioteca (PPL).O fornece PPL contêiner, objetos, e algoritmos comumente usados para executar o paralelismo mais aguçado.

Para obter mais informações sobre o PPL, consulte A modelos paralela a biblioteca (PPL).

Seções

Este documento contém as seções a seguir:

  • Não parallelize corpo de loop pequenos

  • Expresse o paralelismo no nível mais alto possível

  • O parallel_invoke de uso a resolver problemas Partilha-e- conquista

  • Use manipulação de exceção ou cancelar a quebra de um loop paralelo

  • Entenda como o efeito de manipulação de exceção e cancelar a destruição objetos

  • Não bloquear repetidamente em um loop paralelo

  • Não executar operações bloqueantes quando você cancela o trabalho paralelo

  • Não escreva a dados compartilhados em um loop paralelo

  • Quando possível, evite compartilhar false

  • Certifique-se de que as variáveis são válidos em todo o tempo de vida de uma tarefa

Não parallelize corpo de loop pequenos

O parallelization de corpo de loop relativamente pequenos pode fazer com que a sobrecarga associada de programação aumenta os benefícios de processamento paralelo.Considere o exemplo a seguir, que adiciona cada par de elementos em duas matrizes.

// small-loops.cpp
// compile with: /EHsc
#include <ppl.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
   // Create three arrays that each have the same size.
   const size_t size = 100000;
   int a[size], b[size], c[size];

   // Initialize the arrays a and b.
   for (size_t i = 0; i < size; ++i)
   {
      a[i] = i;
      b[i] = i * 2;
   }

   // Add each pair of elements in arrays a and b in parallel 
   // and store the result in array c.
   parallel_for<size_t>(0, size, [&a,&b,&c](size_t i) {
      c[i] = a[i] + b[i];
   });

   // TODO: Do something with array c.
}

A carga de trabalho para cada iteração do loop é paralela muito pequena sobrecarga para se beneficiar de processamento paralelo.Você pode melhorar o desempenho deste loop executando mais trabalho no corpo de loop ou loop executando no intervalo.

Superior[]

Expresse o paralelismo no nível mais alto possível

Quando você parallelize somente no código de baixo nível, você pode gerar uma compilação de forquilha- o join que não redimensiona como o número de processadores aumenta.Uma construção de forquilha- associação é uma compilação onde uma tarefa dividir seu trabalho em subtarefas paralelas menores e espere essas subtarefas para concluir.Cada subtarefa recursivamente pode dividir em subtarefas adicionais.

Embora o modelo de forquilha- o join pode ser útil para resolver uma variedade de problemas, há situações onde a sobrecarga de sincronização pode diminuir a escalabilidade.Por exemplo, considere o seguinte código serial que processa dados de imagem.

// Calls the provided function for each pixel in a Bitmap object.
void ProcessImage(Bitmap* bmp, const function<void (DWORD&)>& f)
{
   int width = bmp->GetWidth();
   int height = bmp->GetHeight();

   // Lock the bitmap.
   BitmapData bitmapData;
   Rect rect(0, 0, bmp->GetWidth(), bmp->GetHeight());
   bmp->LockBits(&rect, ImageLockModeWrite, PixelFormat32bppRGB, &bitmapData);

   // Get a pointer to the bitmap data.
   DWORD* image_bits = (DWORD*)bitmapData.Scan0;

   // Call the function for each pixel in the image.
   for (int y = 0; y < height; ++y)
   {      
      for (int x = 0; x < width; ++x)
      {
         // Get the current pixel value.
         DWORD* curr_pixel = image_bits + (y * width) + x;

         // Call the function.
         f(*curr_pixel);
      }
   }

   // Unlock the bitmap.
   bmp->UnlockBits(&bitmapData);
}

Porque cada iteração do loop é independente, você pode parallelize muito de trabalho, conforme mostrado no exemplo o seguir.Este exemplo usa o algoritmo de concurrency::parallel_for para parallelize o loop mais externo.

// Calls the provided function for each pixel in a Bitmap object.
void ProcessImage(Bitmap* bmp, const function<void (DWORD&)>& f)
{
   int width = bmp->GetWidth();
   int height = bmp->GetHeight();

   // Lock the bitmap.
   BitmapData bitmapData;
   Rect rect(0, 0, bmp->GetWidth(), bmp->GetHeight());
   bmp->LockBits(&rect, ImageLockModeWrite, PixelFormat32bppRGB, &bitmapData);

   // Get a pointer to the bitmap data.
   DWORD* image_bits = (DWORD*)bitmapData.Scan0;

   // Call the function for each pixel in the image.
   parallel_for (0, height, [&, width](int y)
   {      
      for (int x = 0; x < width; ++x)
      {
         // Get the current pixel value.
         DWORD* curr_pixel = image_bits + (y * width) + x;

         // Call the function.
         f(*curr_pixel);
      }
   });

   // Unlock the bitmap.
   bmp->UnlockBits(&bitmapData);
}

O exemplo a seguir ilustra uma compilação de forquilha- o join chamar a função de ProcessImage em um loop.Cada chamada a ProcessImage não retorna até que cada subtarefa terminar.

// Processes each bitmap in the provided vector.
void ProcessImages(vector<Bitmap*> bitmaps, const function<void (DWORD&)>& f)
{
   for_each(begin(bitmaps), end(bitmaps), [&f](Bitmap* bmp) {
      ProcessImage(bmp, f);
   });
}

Se cada iteração do loop paralelo ou não executa quase qualquer trabalho, ou que o trabalho é executado pelo loop paralelo é desequilibrado, isto é, algumas iterações do loop levam mais tempo do que outras pessoas, a sobrecarga de programação que é necessária para se bifurcar e se juntar geralmente ao trabalho pode aumentar o benefício paralela a execução.Essa sobrecarga aumenta a medida que o número de processadores aumenta.

Para reduzir a quantidade de sobrecarga de programação nesse exemplo, você pode parallelize loop externos antes de parallelize loop interno ou use outra construção paralela como o encanamento.O exemplo a seguir altera a função de ProcessImages para usar o algoritmo de concurrency::parallel_for_each para parallelize o loop mais externo.

// Processes each bitmap in the provided vector.
void ProcessImages(vector<Bitmap*> bitmaps, const function<void (DWORD&)>& f)
{
   parallel_for_each(begin(bitmaps), end(bitmaps), [&f](Bitmap* bmp) {
      ProcessImage(bmp, f);
   });
}

Para um exemplo que usa um pipeline semelhante para executar paralelamente o processamento de imagem, consulte Passo a passo: Criando uma rede Processamento de imagens.

Superior[]

O parallel_invoke de uso a resolver problemas Partilha-e- conquista

Um problema de partilha-e- conquista é uma forma de compilação de forquilha- o join usando a recursão para interromper uma tarefa em subtarefas.Além das classes de concurrency::task_group e de concurrency::structured_task_group , você também pode usar o algoritmo de concurrency::parallel_invoke para resolver problemas partilha-e- conquista.O algoritmo de parallel_invoke tem uma sintaxe mais sucinto de objetos de grupo de trabalho, e é útil quando você tem um número fixo de tarefas paralelas.

O exemplo a seguir ilustra o uso do algoritmo de parallel_invoke implementar o algoritmo de classificação bitonic.

// Sorts the given sequence in the specified order.
template <class T>
void parallel_bitonic_sort(T* items, int lo, int n, bool dir)
{   
   if (n > 1)
   {
      // Divide the array into two partitions and then sort 
      // the partitions in different directions.
      int m = n / 2;

      parallel_invoke(
         [&] { parallel_bitonic_sort(items, lo, m, INCREASING); },
         [&] { parallel_bitonic_sort(items, lo + m, m, DECREASING); }
      );

      // Merge the results.
      parallel_bitonic_merge(items, lo, n, dir);
   }
}

Para reduzir a sobrecarga, o algoritmo de parallel_invoke executa o final da série de tarefas no contexto de chamada.

Para a versão completa deste exemplo, consulte Como: Use o parallel_invoke para gravar uma rotina paralela de tipo.Para obter mais informações sobre o algoritmo de parallel_invoke , consulte Algoritmos paralelos.

Superior[]

Use manipulação de exceção ou cancelar a quebra de um loop paralelo

O PPL fornece duas maneiras para cancelar paralelo que o trabalho é executado por um grupo de trabalho ou por um algoritmo paralelo.Uma maneira é usar o mecanismo cancelar que é fornecido pela classes de concurrency::task_group e de concurrency::structured_task_group .Outra maneira é lançar uma exceção no corpo de uma função de trabalho de tarefas.O mecanismo de cancelamento é mais eficiente do que a manipulação de exceção em cancelar uma árvore de trabalho paralelo.Uma árvore paralela de trabalho é um grupo de trabalho relacionados em grupos de que quaisquer grupos de trabalho contém outros grupos de trabalho.O mecanismo de cancelamento cancela um grupo de trabalho e os grupos de trabalho filho de uma maneira top-down.Por outro lado, o trabalho de manipulação de exceção de uma maneira de cima para baixo e devem cancelar cada grupo de trabalho filho independente como as se propaga de exceção para cima.

Quando você trabalha diretamente com um objeto de grupo de trabalho, use os métodos de concurrency::task_group::cancel ou de concurrency::structured_task_group::cancel para cancelar o trabalho que pertence ao grupo de trabalho.Para cancelar um algoritmo paralelo, por exemplo, parallel_for, crie um grupo de trabalho e cancelar um pai que grupo de trabalho.Por exemplo, considere a função a seguir, parallel_find_any, que procura por um valor em uma matriz paralelamente.

// Returns the position in the provided array that contains the given value, 
// or -1 if the value is not in the array.
template<typename T>
int parallel_find_any(const T a[], size_t count, const T& what)
{
   // The position of the element in the array. 
   // The default value, -1, indicates that the element is not in the array.
   int position = -1;

   // Call parallel_for in the context of a cancellation token to search for the element.
   cancellation_token_source cts;
   run_with_cancellation_token([count, what, &a, &position, &cts]()
   {
      parallel_for(std::size_t(0), count, [what, &a, &position, &cts](int n) {
         if (a[n] == what)
         {
            // Set the return value and cancel the remaining tasks.
            position = n;
            cts.cancel();
         }
      });
   }, cts.get_token());

   return position;
}

Porque os algoritmos paralelos usam grupos de trabalho, quando uma de iterações paralelas cancela o grupo de trabalho pai, a tarefa geral é cancelada.Para a versão completa deste exemplo, consulte Como: Use o botão para interromper de um loop paralelo.

Embora a manipulação de exceção é menos maneira eficiente para cancelar o trabalho paralelo de que o mecanismo cancelar, há casos onde a manipulação de exceção é apropriado.Por exemplo, o seguinte método, for_all, execute recursivamente uma função de trabalho em cada nó de uma estrutura de tree .Nesse exemplo, o membro de dados _children é std::list que contém objetos de tree .

// Performs the given work function on the data element of the tree and
// on each child.
template<class Function>
void tree::for_all(Function& action)
{
   // Perform the action on each child.
   parallel_for_each(begin(_children), end(_children), [&](tree& child) {
      child.for_all(action);
   });

   // Perform the action on this node.
   action(*this);
}

O chamador do método de tree::for_all pode acionar uma exceção se não requer a função de trabalho ser chamada cada elemento de árvore.O exemplo a seguir mostra a função de search_for_value , que procura por um valor fornecido em tree objeto.A função de search_for_value usa uma função de trabalho que lança uma exceção quando o elemento atual da árvore corresponde ao valor fornecido.A função de search_for_value usa um bloco de try-catch para capturar a exceção e para imprimir o resultado no console.

// Searches for a value in the provided tree object.
template <typename T>
void search_for_value(tree<T>& t, int value)
{
   try
   {
      // Call the for_all method to search for a value. The work function
      // throws an exception when it finds the value.
      t.for_all([value](const tree<T>& node) {
         if (node.get_data() == value)
         {
            throw &node;
         }
      });
   }
   catch (const tree<T>* node)
   {
      // A matching node was found. Print a message to the console.
      wstringstream ss;
      ss << L"Found a node with value " << value << L'.' << endl;
      wcout << ss.str();
      return;
   }

   // A matching node was not found. Print a message to the console.
   wstringstream ss;
   ss << L"Did not find node with value " << value << L'.' << endl;
   wcout << ss.str();   
}

Para a versão completa deste exemplo, consulte Como: Use manipulação de exceção para interromper de um loop paralelo.

Para obter mais informações gerais sobre os mecanismos de cancelar e de manipulação de exceção que são fornecidos por PPL, consulte Cancelar o PPL e Manipulação de exceção em tempo de execução de concorrência.

Superior[]

Entenda como o efeito de manipulação de exceção e cancelar a destruição objetos

Em uma árvore de trabalho paralelo, uma tarefa que é cancelada evita tarefas filhos de execução.Isso pode causar problemas se uma das tarefas filhos executar uma operação que são importantes para seu aplicativo, como liberar um recurso.Além disso, cancelamento de tarefa pode causar uma exceção à propagação através de um destrutor do objeto e causar comportamento indefinido em seu aplicativo.

No exemplo a seguir, a classe de Resource descreve um recurso e a classe de Container descreve um contêiner que contém recursos.No destrutor, a classe de Container chama o método de cleanup em dois dos seus membros de Resource paralelamente e chama o método de cleanup no terceiro membro de Resource .

// parallel-resource-destruction.h
#pragma once
#include <ppl.h>
#include <sstream>
#include <iostream>

// Represents a resource.
class Resource
{
public:
   Resource(const std::wstring& name)
      : _name(name)
   {
   }

   // Frees the resource.
   void cleanup()
   {
      // Print a message as a placeholder.
      std::wstringstream ss;
      ss << _name << L": Freeing..." << std::endl;
      std::wcout << ss.str();
   }
private:
   // The name of the resource.
   std::wstring _name;
};

// Represents a container that holds resources.
class Container
{
public:
   Container(const std::wstring& name)
      : _name(name)
      , _resource1(L"Resource 1")
      , _resource2(L"Resource 2")
      , _resource3(L"Resource 3")
   {
   }

   ~Container()
   {
      std::wstringstream ss;
      ss << _name << L": Freeing resources..." << std::endl;
      std::wcout << ss.str();

      // For illustration, assume that cleanup for _resource1
      // and _resource2 can happen concurrently, and that 
      // _resource3 must be freed after _resource1 and _resource2.

      concurrency::parallel_invoke(
         [this]() { _resource1.cleanup(); },
         [this]() { _resource2.cleanup(); }
      );

      _resource3.cleanup();
   }

private:
   // The name of the container.
   std::wstring _name;

   // Resources.
   Resource _resource1;
   Resource _resource2;
   Resource _resource3;
};

Embora esse padrão não tem problema em seus próprios, considere o seguinte código que executa duas tarefas paralelamente.A primeira tarefa cria um objeto de Container e a segunda tarefa cancela a tarefa geral.Para a ilustração, o exemplo usa dois objetos de concurrency::event para certificar-se de que o cancelar ocorre depois que o objeto de Container é criado e que o objeto de Container é destruído após a operação de cancelamento ocorre.

// parallel-resource-destruction.cpp
// compile with: /EHsc
#include "parallel-resource-destruction.h"

using namespace concurrency;
using namespace std;

static_assert(false, "This example illustrates a non-recommended practice.");

int main()
{  
   // Create a task_group that will run two tasks.
   task_group tasks;

   // Used to synchronize the tasks.
   event e1, e2;

   // Run two tasks. The first task creates a Container object. The second task
   // cancels the overall task group. To illustrate the scenario where a child 
   // task is not run because its parent task is cancelled, the event objects 
   // ensure that the Container object is created before the overall task is 
   // cancelled and that the Container object is destroyed after the overall 
   // task is cancelled.

   tasks.run([&tasks,&e1,&e2] {
      // Create a Container object.
      Container c(L"Container 1");

      // Allow the second task to continue.
      e2.set();

      // Wait for the task to be cancelled.
      e1.wait();
   });

   tasks.run([&tasks,&e1,&e2] {
      // Wait for the first task to create the Container object.
      e2.wait();

      // Cancel the overall task.
      tasks.cancel();      

      // Allow the first task to continue.
      e1.set();
   });

   // Wait for the tasks to complete.
   tasks.wait();

   wcout << L"Exiting program..." << endl;
}

Esse exemplo produz a seguinte saída.

  
  

Este exemplo de código contém os seguintes problemas que podem fazer com que se comporta de forma diferente que você espera:

  • Cancelamento de tarefa pai faz com que a tarefa filho, a chamada a concurrency::parallel_invoke, também ser cancelado.Portanto, esses dois recursos não são liberados.

  • Cancelamento de tarefa pai faz com que a tarefa filho lançar uma exceção interna.Porque o destrutor de Container não trata essa exceção, a exceção é propagada para cima e o terceiro recurso não é liberado.

  • A exceção que é lançada por tarefa filho propaga pelo destrutor de Container .Lançar de um destrutor colocar o aplicativo em um estado indefinido.

Recomendamos que você não executa operações críticos, como se liberar recursos, as tarefas a menos que você possa garantir que essas tarefas não serão canceladas.Nós também recomendável que você não use a funcionalidade de tempo de execução que pode lançar o destrutor dos seus tipos.

Superior[]

Não bloquear repetidamente em um loop paralelo

Um loop paralelo como concurrency::parallel_for ou concurrency::parallel_for_each que é dominado bloqueando operações pode fazer com que o tempo de execução criar vários segmentos sobre um curto período de tempo.

O tempo de execução de simultaneidade executa o trabalho adicional quando uma tarefa termina ou cooperativa blocos ou passa.Quando os blocos paralelos de uma iteração do loop, o tempo de execução podem iniciar outra iteração.Quando não há nenhum segmento ocioso disponível, o tempo de execução cria um novo segmento.

Quando o corpo de blocos paralelos de um loop ocasionalmente, esse mecanismo ajudar a maximizar a taxa total de tarefas.No entanto, quando o bloco de muitas iterações, o tempo de execução pode criar vários segmentos para realizar o trabalho adicional.Isso pode levar às condições de memória baixa ou para usar ruim de recursos de hardware.

Considere o seguinte exemplo que chama a função de concurrency::send em cada iteração de um loop de parallel_for .Porque os blocos de send cooperativa, o tempo de execução cria um novo thread para realizar o trabalho adicional cada vez que send é chamado.

// repeated-blocking.cpp
// compile with: /EHsc
#include <ppl.h>
#include <agents.h>

using namespace concurrency;

static_assert(false, "This example illustrates a non-recommended practice.");

int main()
{
   // Create a message buffer.
   overwrite_buffer<int> buffer;

   // Repeatedly send data to the buffer in a parallel loop.
   parallel_for(0, 1000, [&buffer](int i) {

      // The send function blocks cooperatively. 
      // We discourage the use of repeated blocking in a parallel
      // loop because it can cause the runtime to create 
      // a large number of threads over a short period of time.
      send(buffer, i);
   });
}

Recomendamos que você refatora o código evitar este padrão.Nesse exemplo, você pode evitar a criação de segmentos adicionais chamando send em um loop serial de for .

Superior[]

Não executar operações bloqueantes quando você cancela o trabalho paralelo

Quando possível, não executar operações bloqueantes antes de chamar o método de concurrency::task_group::cancel ou de concurrency::structured_task_group::cancel para cancelar o trabalho paralelo.

Quando uma tarefa executa uma operação cooperativa de bloqueio, o tempo de execução pode executar outro trabalho à primeira tarefa esperar dados.O tempo de execução reprograma a tarefa de espera quando desbloqueia.O tempo de execução reprograma normalmente as tarefas que foram desbloqueadas mais recentemente antes que reprogramem as tarefas que foram desbloqueadas menos recentemente.Portanto, o tempo de execução pode agendar o trabalho desnecessário durante a operação de bloqueio, que resulta no desempenho diminuído.Da mesma forma, quando você executa uma operação bloqueante antes de cancele o trabalho paralelo, a operação de bloqueio pode atrasar a chamada a cancel.Isso causa outras tarefas executar o trabalho desnecessário.

Considere o seguinte exemplo que define a função de parallel_find_answer , que procura por um elemento de matriz fornecida que satisfaz a função fornecida de predicado.Quando a função de predicado retorna true, a função de trabalho paralela cria um objeto de Answer e cancela a tarefa geral.

// blocking-cancel.cpp
// compile with: /c /EHsc
#include <windows.h>
#include <ppl.h>

using namespace concurrency;

// Encapsulates the result of a search operation.
template<typename T>
class Answer
{
public:
   explicit Answer(const T& data)
      : _data(data)
   {
   }

   T get_data() const
   {
      return _data;
   }

   // TODO: Add other methods as needed.

private:
   T _data;

   // TODO: Add other data members as needed.
};

// Searches for an element of the provided array that satisfies the provided
// predicate function.
template<typename T, class Predicate>
Answer<T>* parallel_find_answer(const T a[], size_t count, const Predicate& pred)
{
   // The result of the search.
   Answer<T>* answer = nullptr;
   // Ensures that only one task produces an answer.
   volatile long first_result = 0;

   // Use parallel_for and a task group to search for the element.
   structured_task_group tasks;
   tasks.run_and_wait([&]
   {
      // Declare the type alias for use in the inner lambda function.
      typedef T T;

      parallel_for<size_t>(0, count, [&](const T& n) {
         if (pred(a[n]) && InterlockedExchange(&first_result, 1) == 0)
         {
            // Create an object that holds the answer.
            answer = new Answer<T>(a[n]);
            // Cancel the overall task.
            tasks.cancel();
         }
      });
   });

   return answer;
}

O operador de new executa uma alocação da heap, que pode bloquear.O tempo de execução executa outro trabalho somente quando a tarefa é executada uma chamada cooperativo de bloqueio, como uma chamada a concurrency::critical_section::lock.

O exemplo a seguir mostra como evitar o trabalho desnecessário, e melhora o desempenho dessa maneira.Este exemplo cancela o grupo de trabalho antes que atribui o armazenamento para o objeto de Answer .

// Searches for an element of the provided array that satisfies the provided
// predicate function.
template<typename T, class Predicate>
Answer<T>* parallel_find_answer(const T a[], size_t count, const Predicate& pred)
{
   // The result of the search.
   Answer<T>* answer = nullptr;
   // Ensures that only one task produces an answer.
   volatile long first_result = 0;

   // Use parallel_for and a task group to search for the element.
   structured_task_group tasks;
   tasks.run_and_wait([&]
   {
      // Declare the type alias for use in the inner lambda function.
      typedef T T;

      parallel_for<size_t>(0, count, [&](const T& n) {
         if (pred(a[n]) && InterlockedExchange(&first_result, 1) == 0)
         {
            // Cancel the overall task.
            tasks.cancel();
            // Create an object that holds the answer.
            answer = new Answer<T>(a[n]);            
         }
      });
   });

   return answer;
}

Superior[]

Não escreva a dados compartilhados em um loop paralelo

O tempo de execução de simultaneidade fornece várias estruturas de dados, por exemplo, concurrency::critical_section, que sincronizam simultânea acesso a dados compartilhados.Essas estruturas de dados são úteis em muitos casos, por exemplo, quando várias tarefas exigem raramente acesso compartilhado a um recurso.

Considere o seguinte exemplo que usa o algoritmo de concurrency::parallel_for_each e um objeto de critical_section para calcular a contagem de números primos em um objeto de std::array .Este exemplo não redimensiona porque cada segmento deve aguardar para acessar prime_sumvariável compartilhada.

critical_section cs;
prime_sum = 0;
parallel_for_each(begin(a), end(a), [&](int i) {
   cs.lock();
   prime_sum += (is_prime(i) ? i : 0);
   cs.unlock();
});

Este exemplo também pode resultar em baixo desempenho porque a operação frequente de bloqueio serializa efetivamente o loop.Além disso, quando um objeto de tempo de execução de simultaneidade executar uma operação de bloqueio, o agendador pode criar um segmento adicional para executar outro trabalho quando o primeiro segmento esperar dados.Se o tempo de execução cria vários segmentos porque muitas tarefas estão aguardando dados compartilhados, o aplicativo pode executar de maneira distorcida ou entrar em um estado de baixo recurso.

O PPL define a classe de concurrency::combinable , que ajuda você elimina o estado compartilhado fornecendo acesso aos recursos compartilhados de forma livre.A classe de combinable fornece o local de armazenamento com o que permite executar cálculos refinados e mesclar nessas cálculos em um resultado final.Você pode pensar um objeto de combinable como um variável mitigação.

O exemplo a seguir altera anterior usando um objeto de combinable em vez de um objeto de critical_section para calcular a soma.Dimensiona este exemplo porque cada segmento contém sua própria cópia local de soma.Este exemplo usa o método de concurrency::combinable::combine para mesclar computações locais no resultado final.

combinable<int> sum;
parallel_for_each(begin(a), end(a), [&](int i) {
   sum.local() += (is_prime(i) ? i : 0);
});
prime_sum = sum.combine(plus<int>());

Para a versão completa deste exemplo, consulte Como: use combinável para melhorar o desempenho.Para obter mais informações sobre a classe combinable, consulte Contêiner e objetos paralelos.

Superior[]

Quando possível, evite compartilhar false

Compartilhar falso ocorre quando várias tarefas simultâneas que estão executando em processadores separados gravam variáveis que estão localizados na mesma linha do cache.Quando uma tarefa grava a uma das variáveis, a linha de cache para ambas as variáveis é invalidada.Cada processador deve recarregar a linha de cache toda vez que a linha de cache é invalidado.Portanto, o compartilhamento falso pode causar o desempenho diminuído em seu aplicativo.

O exemplo a seguir mostra duas tarefas básico simultâneas que cada incremento uma variável de contagem compartilhado.

volatile long count = 0L;
concurrency::parallel_invoke(
   [&count] {
      for(int i = 0; i < 100000000; ++i)
         InterlockedIncrement(&count);
   },
   [&count] {
      for(int i = 0; i < 100000000; ++i)
         InterlockedIncrement(&count);
   }
);

Para eliminar compartilhar de dados entre as duas tarefas, você pode alterar o exemplo para usar duas variáveis do contador.Este exemplo calcula o valor do contador final depois que as tarefas concluírem.No entanto, este exemplo ilustra compartilhar false porque variáveis count1 e count2 são prováveis de serem localizados na mesma linha do cache.

long count1 = 0L;
long count2 = 0L;
concurrency::parallel_invoke(
   [&count1] {
      for(int i = 0; i < 100000000; ++i)
         ++count1;
   },
   [&count2] {
      for(int i = 0; i < 100000000; ++i)
         ++count2;
   }
);
long count = count1 + count2;

Uma maneira para eliminar compartilhar falso é certificar-se de que variáveis do contador estão em linhas separadas do cache.O exemplo a seguir alinha variáveis count1 e count2 em 64 limites de bytes.

__declspec(align(64)) long count1 = 0L;      
__declspec(align(64)) long count2 = 0L;      
concurrency::parallel_invoke(
   [&count1] {
      for(int i = 0; i < 100000000; ++i)
         ++count1;
   },
   [&count2] {
      for(int i = 0; i < 100000000; ++i)
         ++count2;
   }
);
long count = count1 + count2;

Este exemplo assume que o tamanho do cache de memória é 64 ou menos bytes.

Recomendamos que você usa a classe de concurrency::combinable quando você deve compartilhar dados entre tarefas.A classe de combinable cria com variáveis do local de tal forma que o compartilhamento falso é menos provável.Para obter mais informações sobre a classe combinable, consulte Contêiner e objetos paralelos.

Superior[]

Certifique-se de que as variáveis são válidos em todo o tempo de vida de uma tarefa

Quando você fornece uma expressão lambda a um grupo de trabalho ou um algoritmo paralelo, a cláusula catch especifica se o corpo de variáveis de acessos de expressão lambda no escopo delimitador por valor ou por referência.Quando você passa as variáveis a uma expressão lambda por referência, você deve garantir que o tempo de vida do persiste variável até que a tarefa termina.

Considere o seguinte exemplo que define a classe de object e a função de perform_action .A função de perform_action cria um variável de object e executa alguma ação nessa variável de forma assíncrona.Porque a tarefa não é garantida terminar antes que a função de perform_action retorna, o programa falhará ou exibirá o comportamento não especificado se a variável de object é destruído quando a tarefa está sendo executado.

// lambda-lifetime.cpp
// compile with: /c /EHsc
#include <ppl.h>

using namespace concurrency;

// A type that performs an action.
class object
{
public:
   void action() const
   {
      // TODO: Details omitted for brevity.
   }
};

// Performs an action asynchronously.
void perform_action(task_group& tasks)
{
   // Create an object variable and perform some action on 
   // that variable asynchronously.
   object obj;
   tasks.run([&obj] {
      obj.action();
   });

   // NOTE: The object variable is destroyed here. The program
   // will crash or exhibit unspecified behavior if the task
   // is still running when this function returns.
}

Dependendo dos requisitos do seu aplicativo, você pode usar uma das seguintes técnicas garantir que as variáveis permanecem válidos em todo o tempo de vida de cada tarefa.

O exemplo a seguir passa a variável de object pelo valor à tarefa.Portanto, a tarefa opera em sua própria cópia da variável.

// Performs an action asynchronously.
void perform_action(task_group& tasks)
{
   // Create an object variable and perform some action on 
   // that variable asynchronously.
   object obj;
   tasks.run([obj] {
      obj.action();
   });
}

Porque a variável de object é passado por valor, quaisquer alterações de estado que ocorrem a essa variável não aparecem na cópia original.

O exemplo a seguir usa o método de concurrency::task_group::wait para certificar-se que os conclusão da tarefa antes de função de perform_action retornam.

// Performs an action.
void perform_action(task_group& tasks)
{
   // Create an object variable and perform some action on 
   // that variable.
   object obj;
   tasks.run([&obj] {
      obj.action();
   });

   // Wait for the task to finish. 
   tasks.wait();
}

Porque a tarefa termina agora antes que a função retorna, a função de perform_action não se comporta de forma assíncrona.

O exemplo a seguir altera a função de perform_action para obter uma referência à variável de object .O chamador deve garantir que o tempo de vida da variável de object é válido até termina a tarefa.

// Performs an action asynchronously.
void perform_action(object& obj, task_group& tasks)
{
   // Perform some action on the object variable.
   tasks.run([&obj] {
      obj.action();
   });
}

Você também pode usar um ponteiro para controlar o tempo de vida de um objeto que você passe a um grupo de trabalho ou um algoritmo paralelo.

Para obter mais informações sobre expressões lambda, consulte Expressões lambda C++.

Superior[]

Consulte também

Tarefas

Passo a passo: Criando uma rede Processamento de imagens

Como: Use o parallel_invoke para gravar uma rotina paralela de tipo

Como: Use o botão para interromper de um loop paralelo

Como: use combinável para melhorar o desempenho

Conceitos

A modelos paralela a biblioteca (PPL)

Contêiner e objetos paralelos

Algoritmos paralelos

Cancelar o PPL

Manipulação de exceção em tempo de execução de concorrência

Práticas recomendadas na biblioteca assíncrona de agentes

Práticas recomendadas gerais em tempo de execução de concorrência

Outros recursos

Práticas recomendadas em tempo de execução de concorrência