Sdílet prostřednictvím


Doporučené postupy v paralelní knihovny vzorků

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

Další informace týkající PPL, viz Paralelní knihovnu vzorků (PPL).

Oddíly

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

  • Není paralelizovat těla malých smyček

  • Express paralelnost na nejvyšší možné úrovni.

  • Použijte parallel_invoke k řešení problémů dělení a dobytí

  • Zrušení nebo zpracování vyjímek k přerušení z paralelní smyčky

  • Porozumět vlivu zrušení a zpracování výjimek na zničení objektu

  • Opakovaně blokování není v paralelních smyčkách

  • Neprovádějte blokování operací při zrušení paralelní práce

  • Nelze zapisovat do sdílených dat v paralelní smyčka

  • Pokud možno se vyhnout sdílení False

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

Není paralelizovat těla malých smyček

Paralelizace relativně malé smyčce subjekty mohou způsobit přidružené plánování režie převažují nad výhody paralelního zpracování.Zvažte následující příklad přidá každý pár 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.
}

Pracovní vytížení pro každý iterace paralelní smyčky je příliš malá, využívat režii 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.

Top

Express paralelnost na nejvyšší možné úrovni.

Při paralelizovat kód pouze na nízké úrovni, může zavést rozvětvení spojení konstrukce, která není vhodné jako počet procesorů zvýší.A vidlice spojení konstrukce je konstrukce, kde jeden úkol rozdělí své práce na menší paralelní dílčích úkolů a čeká na dokončení těchto dílčích úkolů.Každý dílčí úkol lze rozdělit rekurzivně sama do další dílčí úkoly.

Přestože model rozvětvení spojení může být užitečné při řešení různých problémů, jsou situace, kdy režie synchronizace lze 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ý, paralelizovat velkou část práce, jak je znázorněno v následujícím příkladu.V tomto příkladu concurrency::parallel_for algoritmus paralelizovat 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 rozvětvení 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);
   });
}

Pokud každá iterace paralelní smyčky, buď provádí téměř žádná práce nebo práce, kterou provádí paralelní smyčka imbalanced, to znamená, některé iterací smyčky trvat déle než ostatní, plánování režie, je povinna často rozvětvené a spojení práce může převažují nad přínosem pro paralelní spouštění.Toto zatížení zvyšuje se zvyšováním počtu procesorů zvýšení.

Ke snížení množství plánování režie v tomto příkladu, můžete paralelizovat vnější smyčky před paralelizovat vnitřní smyčky nebo použít jiné paralelní konstrukce, jako je použití kanálů.Následující příklad upravuje ProcessImages funkci, kterou chcete použít concurrency::parallel_for_each algoritmus paralelizovat 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í paralelně provádět zpracování obrazu, viz Názorný postup: Vytváření sítě zpracování obrazu.

Top

Použijte parallel_invoke k řešení problémů dělení a dobytí

A dělení a dobytí problému je forma rozvětvení 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 také použít concurrency::parallel_invoke algoritmus pro dělení a dobytí problémy vyřešit.parallel_invoke Algoritmus má syntaxi stručnější než objekty skupiny úloh a je užitečné, pokud máte pevný počet paralelní úlohy.

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

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

Chcete-li snížit režii, parallel_invoke algoritmus provádí poslední z řady úkolů na kontext volání.

Úplná verze v tomto příkladu naleznete v Postup: zápis paralelní rutinní řazení pomocí parallel_invoke.Další informace o parallel_invoke algoritmus, viz Paralelní algoritmy.

Top

Zrušení nebo zpracování vyjímek k přerušení z paralelní smyčky

PPL poskytuje dva způsoby, jak zrušit paralelní práce, kterou provádí skupina úloh nebo paralelního algoritmu.Jedním ze způsobů je použití zrušení mechanismus, který je poskytován concurrency::task_group a concurrency::structured_task_group třídy.Jiným způsobem je vyvolat výjimku v těle funkce pracovního úkolu.Zrušení mechanismus je efektivnější než při zrušení stromu paralelní práce pro zpracování výjimek.A paralelní práce stromu je skupina skupiny souvisejících úkolů, ve kterých některé skupiny úloh obsahovat jiné skupiny úloh.Mechanismus zrušení zruší skupinu úkolů a podřízených skupin úloh způsobem shora dolů.Naopak zpracování výjimek způsobem zdola nahoru a nutné zrušit každé skupiny podřízeného úkolu nezávisle jako se vyjímka rozšíří 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 metod zrušíte práce, které patří k této skupině úloh.Chcete-li zrušit paralelního algoritmu, například parallel_for, vytvoření skupiny nadřazené úlohy 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;
}

Protože paralelní algoritmy použít skupin úkolů, když jedna iterace paralelní zruší nadřazené skupiny úloh, obecný úkol je zrušena.Úplná verze v tomto příkladu naleznete v Jak: pomocí zrušení přestávky z paralelní smyčka.

Zpracování výjimek je méně efektivní způsob, jak zrušit paralelní práce, než zrušení mechanismus, existují případy, kde je vhodné zpracování výjimek.Například následující metodu, for_all, rekurzivně provede funkci práce v každém uzlu tree struktury.V tomto příkladu _children datový člen je 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 práce funkce, která má být volána na každém prvku stromu.Následující příklad ukazuje search_for_value funkce, která vyhledá hodnotu v poskytované tree objektu.search_for_value Funkci používá funkce práce, která vyvolá výjimku, pokud aktuální prvek stromu odpovídá zadané hodnotě.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();   
}

Úplná verze v tomto příkladu naleznete v Jak: použití zpracování přestávku na paralelní smyčku z výjimek.

Další obecné informace o zrušení a mechanismus zpracování výjimek, které jsou k dispozici PPL, viz Zrušení v PPL a Zpracování výjimek v souběžném běhu.

Top

Porozumět vlivu zrušení a zpracování výjimek na zničení objektu

Ve stromu paralelní pracovní úkol, který je zrušen zabraňuje podřízené úlohy spuštění.To může způsobit potíže, pokud jeden z podřízených úkolů provádí operaci, která je důležitá pro aplikace, jako je například uvolnění prostředku.Kromě toho zrušení úlohy může způsobit výjimku nešíří destruktor objektu a způsobit nedefinované chování ve vaší aplikaci.

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 jeho Resource členy v paralelní a pak zavolá cleanup metoda na jeho třetí Resource člen.

// 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ý spustí dvě úlohy paralelně.První úloha vytvoří Container objekt a druhý úkol zruší obecný úkol.Pro ilustraci v příkladu jsou použity dva concurrency::event objekty, abyste se ujistili, že zrušení dojde po Container je vytvořen objekt a že 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.Tyto dva zdroje, proto nebyla uvolněna.

  • Zrušení nadřazená úloha způsobí, že podřízená úloha vyvolá výjimku vnitřní.Protože Container destruktoru nezpracovává tuto výjimku, vyjímka je rozšířena směrem nahoru a třetí zdroj není uvolněno.

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

Doporučujeme neprovádět kritickou operací, jako je uvolnění prostředků ve složce úkoly, pokud nelze 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.

Top

Opakovaně blokování není v paralelních smyčkách

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

Concurrency Runtime provádí další práce při dokončení úkolu nebo kooperativně za blokuje nebo dává.Když jeden 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ž subjekt paralelní smyčka občas bloky, tento mechanismus zajišťuje maximalizovat celkovou propustnost úkolu.Však po mnoho iterací blokuje, modul runtime může vytvořit mnoho podprocesů, které lze spustit další práce.To může vést k nedostatku paměti nebo špatné využití hardwarových prostředků.

Následující příklad, který volá concurrency::send funkce v každém opakování parallel_for smyčky.Protože send blokuje kooperativně za, modul runtime vytvoří nové vlákno spustit další práce pokaždé, když send je volána.

// 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, abyste Refaktorovat váš kód tak, aby tento vzor.V tomto příkladu, můžete se vyhnout vytvoření dalších vláken voláním send v sériovým for smyčky.

Top

Neprovádějte blokování operací při zrušení paralelní práce

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 provádí družstvo, blokování provozu, modul runtime může provádět jinou práci, zatímco první úkol čeká na data.Modul runtime telefonováním čekající úlohou, když ji odblokuje.Modul runtime obvykle telefonováním úkoly, které byly nedávno odblokování před telefonováním úkoly, které byly méně odblokování.Modul runtime tedy může naplánovat zbytečné práce během blokování provozu, což vede k poklesu výkonu.V důsledku toho při provádění blokující operace před zrušit paralelní práce, blokující operace pozdržet volání cancel.To způsobí, že další úkoly provádět zbytečné práce.

Následující příklad, který definuje parallel_find_answer funkce, která vyhledá prvek zadaného pole, která splňuje zadaná funkce predikátu.Pokud predikátů vrátí true, paralelní pracovní funkce vytvoří Answer objektu 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 vykonávat další práci, pouze v případě, že úkol provede družstvo, blokování volání, jako je například volání concurrency::critical_section::lock.

Následující příklad ukazuje, jak zabránit zbytečné práce a tím zvýšit výkon.V tomto příkladu zruší skupiny úloh před vyhradí úložiště 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;
}

Top

Nelze zapisovat do sdílených dat v paralelní smyčka

Modul Runtime souběžnosti 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ů zřídka vyžadují sdílený přístup k prostředku.

Následující příklad, který používá concurrency::parallel_for_each algoritmu a critical_section objekt, který chcete vypočítat počet prvočísel v std::array objektu.V tomto příkladu není vhodné, protože každé vlákno musí čekat 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 nízký výkon, protože časté operace uzamčení účinně serializuje smyčky.Navíc pokud objekt Concurrency Runtime provádí blokující operace, Plánovač může vytvořit další vlákna k provedení jiné práce, zatímco první podproces čeká na data.Pokud modul runtime vytvoří mnoho podprocesů, protože mnoho úkolů čekají na sdílených dat, můžete aplikaci neprodukuje nebo přechod do stavu nedostatku prostředků.

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

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 změní velikost, 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>());

Úplná verze v tomto příkladu naleznete v Jak: použití combinable zlepšení výkonu.Další informace týkající combinable třídy naleznete v tématu Paralelní kontejnerů a objektů.

Top

Pokud možno se vyhnout sdílení False

False sdílení dojde k více souběžných úkoly, které jsou spuštěny v samostatné procesory zapisovat proměnné, které jsou umístěny na stejném řádku mezipaměti.V případě, že jeden úkol se zapíše do jedné z proměnných, je zrušena platnost řádku mezipaměti pro obě proměnné.Každý procesor musí znovu načíst řádek mezipaměti pokaždé, když řádek mezipaměti je zrušena platnost.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 proměnné sdílené čítače.

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á hodnotu čítače konečné po dokončení úkolů.Avšak tento příklad ilustruje false sdílení, 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;

Abyste se ujistili, že jsou proměnné čítače mezipaměti samostatných řádků je možné eliminovat false sdílení.Následující příklad zarovná proměnné count1 a count2 na 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žívat concurrency::combinable třídy v případě, že je nutné nastavit sdílení dat mezi úkoly.combinable Třídy vytvoří takovým způsobem, že false sdílení je méně pravděpodobné, že vlákna místní proměnné.Další informace týkající combinable třídy naleznete v tématu Paralelní kontejnerů a objektů.

Top

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

Zadáte-li lambda výraz skupiny úloh nebo paralelního algoritmu, zachycení klauzule určuje, zda subjekt lambda výraz přistupuje k proměnné v rámci nadřazeného hodnotou nebo odkazem.Při předání proměnné lambda výraz odkazem, musí zaručit, že životnost dané proměnné ukládá, dokud neskončí úloha.

Následující příklad, který definuje object třídy a perform_action funkce.perform_action Funkce vytvoří object proměnné a provede určitou akci proměnné asynchronně.Protože není zaručen úkol dokončit před perform_action funkce vrátí, bude program crash nebo nespecifikované chovat 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 některý z následujících postupů zaručit, že proměnné zůstávají v platnosti po celou dobu životnosti každého úkolu.

V následujícím příkladu předává object proměnné podle hodnoty úkolu.Proto úkolu pracuje na své vlastní kopie 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é nejsou uvedeny v původní kopie.

Následující příklad používá concurrency::task_group::wait metodu, abyste se ujistili, že na dokončení úlohy 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();
}

Protože nyní neskončí úloha dříve, než se vrátí, perform_action funkce již pracuje asynchronně.

Následující příklad upravuje perform_action funkce odkaz object proměnné.Volající musí zaručit, že existence 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 skupiny úloh nebo paralelního algoritmu.

Další informace o lambda výrazů naleznete v tématu Lambda výrazy v jazyce C++.

Top

Viz také

Úkoly

Názorný postup: Vytváření sítě zpracování obrazu

Postup: zápis paralelní rutinní řazení pomocí parallel_invoke

Jak: pomocí zrušení přestávky z paralelní smyčka

Jak: použití combinable zlepšení výkonu

Koncepty

Paralelní knihovnu vzorků (PPL)

Paralelní kontejnerů a objektů

Paralelní algoritmy

Zrušení v PPL

Zpracování výjimek v souběžném běhu

Doporučené postupy v knihovně asynchronní agenti

Obecné doporučené postupy v souběžném běhu

Další zdroje

Doporučené postupy Runtime souběžnosti