Sdílet prostřednictvím


Osvědčené postupy v knihovně PPL (Parallel Patterns Library)

Tento dokument popisuje, jak nejlépe využít efektivní paralelní knihovnu vzorků (PPL).PPL poskytuje univerzální kontejnery, objekty a algoritmy pro provádění podrobného paralelismu.

Další informace o PPL, viz Knihovna PPL (Parallel Patterns Library).

Oddíly

Tento dokument obsahuje následující oddíly:

  • Nepoužívejte paralelní provádění pro smyčky s krátkým tělem

  • Vyjádřete paralelismus na nejvyšší možné úrovni

  • Použijte algoritmus parallel_invoke k řešení problémů typu Rozděl a panuj

  • Přerušení paralelní smyčky pomocí zrušení nebo zpracování výjimek

  • Pochopení vlivu zrušení a zpracování výjimek na odstraňování objektů

  • Nepoužívejte opakovaně blokování v paralelních smyčkách

  • Neprovádějte blokující operace, když rušíte paralelně prováděné úlohy

  • Nezapisujte do sdílených dat v paralelní smyčce

  • Pokud možno se vyhněte falešnému sdílení

  • Ujistěte se, že proměnné jsou platné po celou dobu trvání úlohy

Nepoužívejte paralelní provádění pro smyčky s krátkým tělem

Paralelního zpracování poměrně malých smyček subjektů může způsobit přidruženého plánování režie převažují nad výhodami paralelní zpracování.Zvažte následující příklad přidá každé dvojice prvků ve dvou maticích.

// 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.
}

Za každé opakování smyčky paralelní pracovní vytížení je příliš malá využívat režie pro paralelní zpracování.Můžete zlepšit výkon této smyčky provedením více práce do těla smyčky nebo sériově provádění smyčky.

[Nahoře]

Vyjádřete paralelismus na nejvyšší možné úrovni

Když je paralelní kód pouze na nízké úrovni, může zavést větev spojení konstrukce, která není adekvátní počet procesorů zvýší.A vidlice spojení konstrukce je konstrukce, kde jeden úkol rozdělí na menší dílčí paralelní svou práci a čeká na dokončení těchto dílčích úkolů.Každý dílčí úkol může rekurzivně dělení sám do další dílčí úkoly.

Přestože model fork spojení mohou být užitečné při řešení různých problémů, jsou situace, kdy režie synchronizace může snížit škálovatelnost.Zvažte například následující sériový kód, který zpracovává data obrázku.

// 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);
}

Protože každé opakování smyčky je nezávislá, je velká část práce, jak je znázorněno v následujícím příkladu paralelizovat.V tomto příkladu concurrency::parallel_for algoritmus pro paralelní vnější smyčky.

// 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);
}

Následující příklad ukazuje konstrukci větev spojení voláním ProcessImage funkce ve smyčce.Každé volání ProcessImage nevrací až do dokončení jednotlivých dílčích úkolů.

// 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);
   });
}

Každé iteraci buď provádí téměř bez práce nebo práce, kterou provádí paralelní smyčka paralelní smyčka je imbalanced, to znamená, některých iterací smyčky trvat déle než ostatní, plánování zatížení, je nutné často rozvětvené a spojení práce předčit výhodu pro paralelní spouštění.Toto zatížení zvětšuje počet procesorů zvýší.

Pro snížení zatížení v tomto příkladu plánování, můžete paralelizovat vnější smyčky paralelní vnitřní smyčky nebo použití jiné paralelní konstrukce, jako je například použití kanálů.Následující příklad upravuje ProcessImages funkce pro použití concurrency::parallel_for_each algoritmus pro paralelní vnější smyčky.

// 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);
   });
}

Podobný příklad, který používá potrubí k provedení zpracování obrazu paralelně, viz Návod: Vytvoření sítě pro zpracování obrázků.

[Nahoře]

Použijte algoritmus parallel_invoke k řešení problémů typu Rozděl a panuj

A dělení a dobytí problém je forma větev spojení konstrukce, která používá rekurzi k rozdělují úkoly na dílčí úkoly.Navíc concurrency::task_group a concurrency::structured_task_group třídy, můžete použít také concurrency::parallel_invoke algoritmus dělení a dobytí problémy vyřešit.parallel_invoke Algoritmus má stručnější syntaxi než objekty skupiny úkolů a je užitečné, pokud máte pevný počet paralelní úlohy.

Následující příklad ukazuje použití parallel_invoke bitonic algoritmus řazení implementace algoritmu.

// 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);
   }
}

Snížit nároky parallel_invoke algoritmus provede poslední řadu úkolů v kontextu volajícího.

Pro úplnou verzi tohoto příkladu, viz Postupy: Použití algoritmu parallel_invoke k zápisu rutiny paralelního třídění.Další informace o parallel_invoke algoritmus, viz Paralelní algoritmy.

[Nahoře]

Přerušení paralelní smyčky pomocí zrušení nebo zpracování výjimek

PPL poskytuje dva způsoby, jak zrušit paralelní práce, kterou provádí skupinu úkolů nebo paralelního algoritmu.Jedním ze způsobů je použití mechanismus zrušení, která je poskytována concurrency::task_group a concurrency::structured_task_group třídy.Druhý způsob je vyvolat výjimku v těle funkce pracovního úkolu.Mechanismus zrušení je efektivnější než na zrušení stromu paralelní práce pro zpracování výjimek.A paralelní práce strom je skupina souvisejících úloh skupin, v nichž některé skupiny úloh obsahovat jiné skupiny úloh.Mechanismus zrušení zruší skupinu úloh a podřízených skupin úloh způsobem shora dolů.Zpracování výjimek naopak funguje způsobem zdola nahoru a zrušit každou podřízenou skupinu úloh samostatně jako výjimka se šíří směrem nahoru.

Při práci přímo s objekt skupiny úloh použít concurrency::task_group::cancel nebo concurrency::structured_task_group::cancel metody zrušit práce, které patří k této skupině úloh.Chcete-li zrušit paralelního algoritmu, například parallel_for, vytvořte skupinu nadřazeného úkolu a zrušit tuto skupinu úloh.Zvažte například následující funkce parallel_find_any, který vyhledává hodnoty v matici paralelně.

// 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;
}

Vzhledem k tomu, že paralelní algoritmy použít skupiny úkolů, když jedna iterace paralelní zruší nadřazené skupiny úloh, obecný úkol je zrušena.Pro úplnou verzi tohoto příkladu, viz Postupy: Přerušení paralelní smyčky pomocí zrušení.

Zpracování výjimek je méně efektivní způsob, jak zrušit paralelní práce než mechanismus zrušení, jsou případy, kde je vhodné zpracování výjimek.Například následující metoda for_all, rekurzivně provede funkci práce v každém uzlu tree struktury.V tomto příkladu _children je datový člen std::list , který obsahuje tree objekty.

// 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);
}

Volající tree::for_all metoda může vyvolat výjimku, pokud nevyžaduje funkci práce má být volána na každém prvku stromu.Následující příklad ukazuje search_for_value funkci, která vyhledá danou hodnotu v poskytované tree objektu.search_for_value Funkce používá pracovní funkci, která vyvolá výjimku, pokud aktuální prvek stromu shoduje se zadanou hodnotou.search_for_value Funkce používá try-catch blok zachytit výjimku a tisknout výsledek do konzoly.

// 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();   
}

Pro úplnou verzi tohoto příkladu, viz Postupy: Přerušení paralelní smyčky pomocí zpracování výjimek.

Další obecné informace o zrušení a mechanismus zpracování výjimek, které jsou k dispozici PPL, viz Zrušení v knihovně PPL a Zpracování výjimek v Concurrency Runtime.

[Nahoře]

Pochopení vlivu zrušení a zpracování výjimek na odstraňování objektů

Ve stromové struktuře paralelní pracovní úkol, který je zrušen zabrání spuštění podřízených úloh.To může způsobit potíže, pokud některá z podřízených úloh provádí operaci, která je důležitá pro vaši aplikaci, jako je například uvolnění prostředku.Navíc zrušení úlohy může způsobit výjimku šíří prostřednictvím destruktor objektu a způsobit nedefinované chování aplikace.

V následujícím příkladu Resource třída popisuje prostředek a Container třída popisuje kontejner, který obsahuje prostředky.V jeho destruktor Container třídy volání cleanup metoda na dvou z jeho Resource členové v parallel a pak zavolá cleanup metoda na jeho třetí Resource členské.

// 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;
};

Přestože tento vzor má sám žádné problémy, zvažte následující kód, který běží dva úkoly současně.Vytvoří první úkol Container objektu a druhá úloha zruší obecný úkol.Pro ilustraci příklad používá dva concurrency::event objekty a ujistěte se, že zrušení dojde po Container objekt je vytvořen a Container objekt zničen po zrušení operace.

// 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;
}

Tento příklad vytvoří následující výstup:

  

Tento příklad kódu obsahuje následující problémy, které mohou způsobit chovají jinak než očekáváte:

  • Zrušení nadřazená úloha podřízená úloha, volání způsobí, že concurrency::parallel_invoke, také budou zrušeny.Proto tyto dva zdroje nebyla uvolněna.

  • Zrušení nadřazeného úkolu způsobí, že podřízená úloha pro vnitřní výjimku.Vzhledem k tomu, Container destruktor nezpracovává tuto výjimku, výjimka je rozšířena směrem nahoru a třetí zdroj není uvolněno.

  • Výjimku, která vyvolá podřízená úloha šíří prostřednictvím Container destruktor.Vyvolání z destruktoru umístí aplikace nedefinovaný stav.

Doporučujeme neprovádět kritické operace, například uvolnění zdroje ve složce úkoly, pokud lze zaručit, že tyto úkoly nebudou zrušeny.Doporučujeme také používat funkce za běhu, která může vyvolat v destruktoru vaše typy.

[Nahoře]

Nepoužívejte opakovaně blokování v paralelních smyčkách

Paralelní smyčky, jako concurrency::parallel_for nebo concurrency::parallel_for_each který je ovládáno blokování operací může způsobit, že modul runtime prostřednictvím krátké době vytvořit mnoho podprocesů.

Concurrency Runtime provádí další práce po dokončení úkolu nebo kooperativně blokuje nebo dává.Při jedné paralelní smyčka iterací blokuje, modul runtime může začít jinou iteraci.Pokud neexistují žádné dostupné nečinné podprocesy, modul runtime vytvoří nové vlákno.

Když tělo paralelní smyčka občas bloky, tento mechanismus pomáhá maximalizovat celkovou propustnost úkolu.Však po mnoho iterací blokuje, modul runtime může vytvořit mnoho podprocesů spustit další práce.To může vést k nedostatku paměti nebo špatné využití hardwarových prostředků.

Vezměte si následující příklad, který volá concurrency::send funkce v jednotlivých opakováních parallel_for smyčky.Vzhledem k tomu, send blokuje kooperativně, modul runtime vytvoří nový podproces spuštění další práce pokaždé, když send se nazývá.

// 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);
   });
}

Doporučujeme, aby je refaktorovat kód k zamezení tohoto vzoru.V tomto příkladu se můžete vyhnout vytváření další podprocesy voláním send v sériovým for smyčky.

[Nahoře]

Neprovádějte blokující operace, když rušíte paralelně prováděné úlohy

Pokud je to možné, neprovádějte blokující operace před voláním concurrency::task_group::cancel nebo concurrency::structured_task_group::cancel metoda zrušit paralelní práce.

Pokud úkol provede družstvo, blokování provozu, modul runtime provádět další práce během prvního úkolu čeká na data.Modul runtime přeplánuje úkol čeká při jeho odblokuje.Modul runtime obvykle telefonováním úkoly, které byly nedávno odblokování před změní splátkový plán úkolů, které byly méně odblokována.Modul runtime může tedy naplánovat zbytečné práce během blokování provozu, což vede k poklesu výkonu.Proto, když provádíte blokující operace před zrušit paralelní práce, blokující operace může zpozdit volání cancel.To způsobí, že další úkoly provádět zbytečné práce.

Vezměte si následující příklad, který definuje parallel_find_answer funkci, která vyhledá prvek zadaného pole, která splňuje zadané predikátu funkce.Pokud predikátů vrátí true, paralelní pracovní funkce vytvoří Answer objekt a zruší obecný úkol.

// 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;
}

new Operátor provádí přidělení haldy, které by mohly znemožnit.Modul runtime provádí jiné činnosti pouze v případě, že úkol provede družstvo, blokování volání, například volání concurrency::critical_section::lock.

Následující příklad ukazuje, jak zabránit zbytečné práce a tím zlepšit výkon.V tomto příkladu zruší skupiny úloh před jeho alokuje prostor pro Answer objektu.

// 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;
}

[Nahoře]

Nezapisujte do sdílených dat v paralelní smyčce

Concurrency Runtime poskytuje několik datových struktur, například concurrency::critical_section, která synchronizaci souběžný přístup ke sdíleným datům.Tyto datové struktury jsou užitečné v mnoha případech, například při více úkolů často vyžadují sdílený přístup k prostředku.

Zvažte následující příklad, který používá concurrency::parallel_for_each algoritmus a critical_section objekt, který chcete vypočítat počet prvočísel v std::array objektu.V tomto příkladu není adekvátní vzhledem k tomu, že každé vlákno musí vyčkat na přístup sdílené proměnné prime_sum.

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();
});

V tomto příkladu může také vést ke snížení výkonu protože časté operace uzamčení jsou účinně smyčky.Navíc pokud objekt Concurrency Runtime provádí blokující operace, Plánovač může vytvořit vlákno, provádět další práce při první podproces čeká na data.Pokud modul runtime vytvoří mnoho podprocesů, protože mnoho úkoly čekají na sdílených dat, aplikace můžete provádět špatně nebo přechod do stavu nedostatku prostředků.

Definuje PPL concurrency::combinable třída, která pomáhá eliminovat tím, že poskytuje přístup ke sdíleným prostředkům způsobem uvolnění zámku sdíleného stavu.combinable Třída poskytuje místní úložiště, která umožňuje provádět detailní výpočty a poté sloučit tyto výpočty do konečného výsledku.Si lze představit combinable jako snížení proměnné objektu.

Následující příklad upravuje předchozí pomocí combinable objekt místo critical_section objekt, který chcete vypočítat součet.V tomto příkladu upraví, protože každé vlákno má svůj vlastní místní kopii součet.V tomto příkladu concurrency::combinable::combine metoda sloučit místní výpočty do konečného výsledku.

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>());

Pro úplnou verzi tohoto příkladu, viz Postupy: Použití objektu combinable ke zlepšení výkonu.Další informace o třídě combinable naleznete v tématu Paralelní kontejnery a objekty.

[Nahoře]

Pokud možno se vyhněte falešnému sdílení

Sdílení false dojde k více souběžných úloh spuštěných v samostatné procesory zapisovat proměnné, které jsou umístěny na stejném řádku mezipaměti.Když jeden úkol zapíše do jedné z proměnných, u obou proměnných řádku mezipaměti zneplatněna.Každý procesor načtení řádku mezipaměti při každém řádku mezipaměti zneplatněna.Proto false sdílení může způsobit snížení výkonu v aplikaci.

Následující základní příklad ukazuje dva souběžné úlohy každé zvýší hodnotu čítače sdílené proměnné.

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);
   }
);

K odstranění sdílení dat mezi dvěma úkoly, můžete upravit příklad použití dvou proměnných čítače.Tento příklad vypočítá hodnota čítače konečné po dokončení úkolů.Tento příklad ukazuje však sdílení false protože proměnné count1 a count2 by mohly být umístěny na stejném řádku mezipaměti.

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;

Jedním ze způsobů odstranění sdílení false je Ujistěte se, že jsou proměnné čítače mezipaměti samostatných řádků.V následujícím příkladu zarovnává proměnné count1 a count2 v rozsahu 64 bajtů.

__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;

Tento příklad předpokládá, že velikost mezipaměti je 64 bajtů.

Doporučujeme použít concurrency::combinable třídy, pokud je nutné nastavit sdílení dat mezi úkoly.combinable Třídy vytvoří vlákna místní proměnné tak, že je méně pravděpodobné, že sdílení false.Další informace o třídě combinable naleznete v tématu Paralelní kontejnery a objekty.

[Nahoře]

Ujistěte se, že proměnné jsou platné po celou dobu trvání úlohy

Lambda výraz zadáte skupinu úkolů nebo paralelního algoritmu, zachycení klauzule určuje, zda tělo lambda výraz získá přístup k proměnné v oboru nadřazené hodnotou nebo odkazem.Při předání proměnných do výrazu lambda pomocí odkazu musíte zaručit, že životnost proměnné potrvá, dokud neskončí úloha.

Vezměte si následující příklad, který definuje object třída a perform_action funkce.perform_action Funkce vytvoří object proměnné a provede určitou akci proměnné asynchronně.Vzhledem k tomu, že není zaručeno, že úkol dokončit před perform_action funkce vrátí program crash či chovat nespecifikované Pokud object proměnné je zničeno, když je úloha spuštěna.

// 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.
}

V závislosti na požadavcích aplikace můžete použít jeden z následujících postupů zajistit, že proměnné zůstávají platné po celou dobu trvání každého úkolu.

Následující příklad projde object proměnné hodnotou k úkolu.Úkol tedy pracuje na své vlastní kopii proměnné.

// 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();
   });
}

Protože object proměnné je předán podle hodnoty, změny stavu, ke kterým dochází k této proměnné se nezobrazují v původní kopie.

V následujícím příkladu concurrency::task_group::wait metoda a ujistěte se, že dokončení úkolu před perform_action funkce vrátí.

// 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();
}

Vzhledem k tomu, že tato úloha nyní skončí dříve, než se vrátí, perform_action funkce již pracuje asynchronně.

Následující příklad upravuje perform_action odkaz na funkce object proměnné.Volající musí zaručit, že životnost object proměnné je platná, dokud neskončí úloha.

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

Ukazatel můžete použít také k řízení životnosti objektu, který předáte do skupiny úloh nebo paralelního algoritmu.

Další informace o výrazech lambda naleznete v tématu Výrazy lambda v jazyce C++.

[Nahoře]

Viz také

Úkoly

Návod: Vytvoření sítě pro zpracování obrázků

Postupy: Použití algoritmu parallel_invoke k zápisu rutiny paralelního třídění

Postupy: Přerušení paralelní smyčky pomocí zrušení

Postupy: Použití objektu combinable ke zlepšení výkonu

Koncepty

Knihovna PPL (Parallel Patterns Library)

Paralelní kontejnery a objekty

Paralelní algoritmy

Zrušení v knihovně PPL

Zpracování výjimek v Concurrency Runtime

Osvědčené postupy v knihovně asynchronních agentů

Obecné osvědčené postupy v Concurrency Runtime

Další zdroje

Osvědčené postupy Concurrency Runtime