Paralelní kontejnery a objekty

Knihovna PPL (Parallel Patterns Library) obsahuje několik kontejnerů a objektů, které poskytují přístup k prvkům bezpečným pro přístup z více vláken.

Souběžný kontejner poskytuje bezpečný přístup k nejdůležitějším operacím za podmínek souběžnosti. V této chvíli souběžnost znamená, že ukazatele nebo iterátory jsou vždy platné. Nejedná se o záruku inicializace prvků ani konkrétního pořadí procházení. Funkce těchto kontejnerů se podobají funkcím, které poskytuje standardní knihovna jazyka C++. Například souběžnost::concurrent_vector třída se podobá třídě std::vector s tím rozdílem, že concurrent_vector třída umožňuje připojit prvky paralelně. Souběžné kontejnery používejte, pokud máte paralelní kód, který vyžaduje přístup ke stejnému kontejneru pro čtení i zápis.

Souběžný objekt se sdílí souběžně mezi komponentami. Proces, který paralelně vypočítá stav souběžného objektu, vytvoří stejný výsledek jako jiný proces, který vypočítá stejný stav sériově. Concurrency::combinable třída je jedním z příkladů souběžného typu objektu. Třída combinable umožňuje provádět výpočty paralelně a pak tyto výpočty zkombinovat do konečného výsledku. Použijte souběžné objekty v případě, že byste jinak použili synchronizační mechanismus, například mutex, pro synchronizaci přístupu ke sdílené proměnné nebo prostředku.

Oddíly

Toto téma podrobně popisuje následující paralelní kontejnery a objekty.

Souběžné kontejnery:

Souběžné objekty:

concurrent_vector Class

Concurrency::concurrent_vector třída je třída kontejneru sekvence, která stejně jako std::vector třída umožňuje náhodný přístup k jeho prvkům. Třída concurrent_vector umožňuje vláknově bezpečné operace připojení a přístupu k prvkům. Operace připojení nezneplatní existující ukazatele ani iterátory. Přístup iterátoru a operace procházení jsou také bezpečné pro souběžné operace. V této chvíli souběžnost znamená, že ukazatele nebo iterátory jsou vždy platné. Nejedná se o záruku inicializace prvků ani konkrétního pořadí procházení.

Rozdíly mezi concurrent_vector a vektorem

Třída concurrent_vector se velmi podobá vector třídě. Složitost operací připojení, přístupu k prvkům a iterátoru u objektu concurrent_vector jsou stejné jako u objektu vector . Následující body ukazují, kde concurrent_vector se liší od vector:

  • Operace připojení, přístupu k prvkům, přístupu iterátoru a procházení iterátoru u objektu concurrent_vector jsou bezpečné pro souběžnost.

  • Prvky lze přidat pouze na konec objektu concurrent_vector . Třída concurrent_vector neposkytuje metodu insert .

  • Objekt concurrent_vector nepoužívá sémantiku přesunu při připojování k němu.

  • Třída concurrent_vector neposkytuje erase ani pop_back metody. Stejně jako u vector, použijte jasnou metodu k odebrání všech prvků z objektuconcurrent_vector.

  • Třída concurrent_vector neukládá své prvky souvisle do paměti. Proto nelze použít třídu concurrent_vector všemi způsoby, jak lze použít pole. Například pro proměnnou s názvem v typu concurrent_vectorvýraz &v[0]+2 vytváří nedefinované chování.

  • Třída concurrent_vector definuje grow_by a grow_to_at_least metody. Tyto metody se podobají metodě změny velikosti , s tím rozdílem, že jsou bezpečné pro souběžnost.

  • Objekt concurrent_vector nepřemístí jeho prvky, když k němu připojíte nebo změníte jeho velikost. To umožňuje, aby stávající ukazatele a iterátory zůstaly platné během souběžných operací.

  • Modul runtime nedefinuje specializovanou verzi concurrent_vector pro typ bool.

Operace bezpečné souběžnosti

Všechny metody, které připojují nebo zvětšují velikost objektu concurrent_vector nebo přistupují k prvku v objektu concurrent_vector , jsou bezpečné pro souběžnost. V této chvíli souběžnost znamená, že ukazatele nebo iterátory jsou vždy platné. Nejedná se o záruku inicializace prvků ani konkrétního pořadí procházení. Výjimkou tohoto pravidla je resize metoda.

Následující tabulka uvádí běžné concurrent_vector metody a operátory, které jsou bezpečné pro souběžnost.

Operace, které modul runtime zajišťuje pro kompatibilitu se standardní knihovnou C++, například reserve, nejsou bezpečné pro paralelní zpracování. Následující tabulka uvádí běžné metody a operátory, které nejsou bezpečné pro souběžnost.

Operace, které upravují hodnotu existujících prvků, nejsou bezpečné pro souběžnost. Pomocí synchronizačního objektu , jako je například objekt reader_writer_lock , můžete synchronizovat souběžné operace čtení a zápisu do stejného datového prvku. Další informace o synchronizačních objektech naleznete v tématu Synchronizační datové struktury.

Při převodu existujícího kódu, který se používá vector k použití concurrent_vector, můžou souběžné operace způsobit změnu chování aplikace. Představte si například následující program, který současně provádí dva úkoly na objektu concurrent_vector . První úkol připojí k objektu concurrent_vector další prvky. Druhý úkol vypočítá součet všech prvků ve stejném objektu.

// parallel-vector-sum.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_vector.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
   // Create a concurrent_vector object that contains a few
   // initial elements.
   concurrent_vector<int> v;
   v.push_back(2);
   v.push_back(3);
   v.push_back(4);
   
   // Perform two tasks in parallel.
   // The first task appends additional elements to the concurrent_vector object.
   // The second task computes the sum of all elements in the same object.

   parallel_invoke(
      [&v] { 
         for(int i = 0; i < 10000; ++i)
         {
            v.push_back(i);
         }
      },
      [&v] {
         combinable<int> sums;
         for(auto i = begin(v); i != end(v); ++i) 
         {
            sums.local() += *i;
         }     
         wcout << L"sum = " << sums.combine(plus<int>()) << endl;
      }
   );
}

end Ačkoli je metoda bezpečná při souběžném provádění, souběžné volání metody push_back způsobí změnu hodnoty vrácené end. Počet prvků, které iterátor prochází, je neurčitý. Proto může tento program při každém spuštění vytvořit jiný výsledek. Pokud typ prvku není triviální, je možné, že mezi voláním push_back a end existuje závodní podmínka. Metoda end může vrátit prvek, který je přidělen, ale není plně inicializován.

Bezpečnost výjimek

Pokud operace růstu nebo přiřazení vyvolá výjimku, stav objektu concurrent_vector bude neplatný. Chování objektu concurrent_vector , který je v neplatném stavu, není definován, pokud není uvedeno jinak. Destruktor však vždy uvolní paměť, kterou objekt přidělí, i když je objekt v neplatném stavu.

Datový typ vektorových prvků musí Tsplňovat následující požadavky. V opačném případě je chování concurrent_vector třídy nedefinováno.

  • Destruktor nesmí vyvolat.

  • Pokud dojde k vyvolání výchozího konstruktoru nebo konstruktoru kopírování, nesmí být destruktor deklarován pomocí klíčového virtual slova a musí správně fungovat s nulou inicializovanou pamětí.

[Nahoře]

concurrent_queue – třída

Třída concurrency::concurrent_queue , stejně jako třída std::queue , umožňuje přístup k jeho předním a zadním prvkům. Třída concurrent_queue umožňuje bezpečné souběžné zařazování do fronty a odebírání z fronty. V této chvíli souběžnost znamená, že ukazatele nebo iterátory jsou vždy platné. Nejedná se o záruku inicializace prvků ani konkrétního pořadí procházení. Třída concurrent_queue také poskytuje podporu iterátoru, která není bezpečná pro souběžnost.

Rozdíly mezi concurrent_queue a queue

Třída concurrent_queue se velmi podobá queue třídě. Následující body ukazují, kde concurrent_queue se liší od queue:

  • Operace zařazení do fronty a odebrání z fronty u concurrent_queue objektu jsou bezpečné pro konkurentní operace.

  • Třída concurrent_queue poskytuje podporu iterátoru, která není bezpečná pro souběžnost.

  • Třída concurrent_queue neposkytuje front ani pop metody. Třída concurrent_queue nahrazuje tyto metody definováním try_pop metody.

  • Třída concurrent_queue neposkytuje metodu back . Proto nelze odkazovat na konec fronty.

  • Třída concurrent_queue poskytuje metodu unsafe_size namísto metody size. Metoda unsafe_size není bezpečná pro souběžnost.

Operace bezpečné souběžnosti

Všechny metody, které vkládají do fronty nebo odstraní z objektu concurrent_queue, jsou bezpečné při souběhu. V této chvíli souběžnost znamená, že ukazatele nebo iterátory jsou vždy platné. Nejedná se o záruku inicializace prvků ani konkrétního pořadí procházení.

Následující tabulka uvádí běžné concurrent_queue metody a operátory, které jsou bezpečné pro souběžnost.

empty I když je metoda bezpečná pro souběžnost, souběžná operace může způsobit zvětšení nebo zmenšení fronty před vrácením empty metody.

Následující tabulka uvádí běžné metody a operátory, které nejsou bezpečné pro souběžnost.

Podpora iterátoru

Objekt concurrent_queue poskytuje iterátory, které nejsou bezpečné pro paralelní zpracování. Tyto iterátory doporučujeme používat pouze pro ladění.

concurrent_queue Iterátor prochází prvky pouze směrem dopředu. Následující tabulka ukazuje operátory, které každý iterátor podporuje.

Operátor Popis
operator++ Přejde na další položku ve frontě. Tento operátor je přetížen tak, aby poskytoval sémantiku před přírůstkem i po přírůstku.
operator* Načte odkaz na aktuální položku.
operator-> Načte ukazatel na aktuální položku.

[Nahoře]

concurrent_unordered_map – třída

Třída concurrency::concurrent_unordered_map je asociativní třída kontejneru, která, stejně jako třída std::unordered_map, řídí sekvenci prvků proměnlivé délky typu std::pair<const Key, Ty>. Neuspořádanou mapu si můžete představit jako slovník, do kterého můžete přidat pár klíč a hodnota nebo vyhledat hodnotu podle klíče. Tato třída je užitečná, pokud máte více vláken nebo úloh, které musí současně přistupovat ke sdílenému kontejneru, vložit do něj nebo je aktualizovat.

Následující příklad ukazuje základní strukturu pro použití concurrent_unordered_map. Tento příklad vloží klíče znaků do oblasti ['a', 'i']. Vzhledem k tomu, že pořadí operací není definováno, je také neurčitá konečná hodnota každého klíče. Je však bezpečné provádět vložení paralelně.

// unordered-map-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_map.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain() 
{
    //
    // Insert a number of items into the map in parallel.

    concurrent_unordered_map<char, int> map; 

    parallel_for(0, 1000, [&map](int i) {
        char key = 'a' + (i%9); // Geneate a key in the range [a,i].
        int value = i;          // Set the value to i.
        map.insert(make_pair(key, value));
    });

    // Print the elements in the map.
    for_each(begin(map), end(map), [](const pair<char, int>& pr) {
        wcout << L"[" << pr.first << L", " << pr.second << L"] ";
    });
}
/* Sample output:
    [e, 751] [i, 755] [a, 756] [c, 758] [g, 753] [f, 752] [b, 757] [d, 750] [h, 754]
*/

Příklad, který používá concurrent_unordered_map k paralelnímu provádění operace mapování a redukce, najdete v tématu Postupy: Provádění operací mapování a redukce paralelně.

Rozdíly mezi concurrent_unordered_map a unordered_map

Třída concurrent_unordered_map se velmi podobá unordered_map třídě. Následující body ukazují, kde concurrent_unordered_map se liší od unordered_map:

  • Metody erase, bucket, bucket_count a bucket_size jsou pojmenovány unsafe_erase, unsafe_bucket, unsafe_bucket_count a unsafe_bucket_size v uvedeném pořadí. Konvence unsafe_ vytváření názvů značí, že tyto metody nejsou bezpečné pro souběžnost. Další informace o bezpečnosti souběžnosti naleznete v tématu Souběžnost-Bezpečné operace.

  • Operace vložení nezneplatňují stávající ukazatele ani iterátory, ani nemění pořadí položek, které již existují v mapě. Operace vložení a procházení můžou probíhat souběžně.

  • concurrent_unordered_map podporuje pouze iteraci vpřed.

  • Vložení zneplatní ani neaktualizuje iterátory, které vrací equal_range. Vložení může připojit nerovné položky na konec rozsahu. Počáteční iterátor odkazuje na stejnou položku.

Aby se zabránilo vzájemnému zablokování, žádná metoda concurrent_unordered_map neudržuje zámek při volání alokátoru paměti, funkcí hash nebo jiného uživatelem definovaného kódu. Také je nutné zajistit, aby funkce hash vždy vyhodnocuje stejné klíče se stejnou hodnotou. Nejlepší hashovací funkce distribuují klíče rovnoměrně napříč prostorem kódu hash.

Operace bezpečné souběžnosti

Třída concurrent_unordered_map umožňuje bezpečné operace souběžného vložení a přístupu k prvkům. Operace vložení nezneplatní existující ukazatele ani iterátory. Přístup iterátoru a operace procházení jsou také bezpečné pro souběžné operace. V této chvíli souběžnost znamená, že ukazatele nebo iterátory jsou vždy platné. Nejedná se o záruku inicializace prvků ani konkrétního pořadí procházení. Následující tabulka uvádí běžně používané concurrent_unordered_map metody a operátory, které jsou bezpečné pro souběžnost.

I když lze metodu count volat bezpečně z souběžně spuštěných vláken, různá vlákna mohou přijímat různé výsledky, pokud je do kontejneru současně vložena nová hodnota.

Následující tabulka uvádí běžně používané metody a operátory, které nejsou bezpečné pro souběžnost.

Kromě těchto metod není žádná metoda, která začíná unsafe_, bezpečná pro souběžný chod.

[Nahoře]

concurrent_unordered_multimap – třída

Třída concurrency::concurrent_unordered_multimap úzce připomíná concurrent_unordered_map třídu, ale umožňuje mapování více hodnot na stejný klíč. Liší se také od concurrent_unordered_map následujícími způsoby:

Následující příklad ukazuje základní strukturu pro použití concurrent_unordered_multimap. Tento příklad vloží klíče znaků do oblasti ['a', 'i']. concurrent_unordered_multimap umožňuje, aby klíč měl více hodnot.

// unordered-multimap-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_map.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain() 
{
    //
    // Insert a number of items into the map in parallel.

    concurrent_unordered_multimap<char, int> map; 

    parallel_for(0, 10, [&map](int i) {
        char key = 'a' + (i%9); // Geneate a key in the range [a,i].
        int value = i;          // Set the value to i.
        map.insert(make_pair(key, value));
    });

    // Print the elements in the map.
    for_each(begin(map), end(map), [](const pair<char, int>& pr) {
        wcout << L"[" << pr.first << L", " << pr.second << L"] ";
    });
}
/* Sample output:
    [e, 4] [i, 8] [a, 9] [a, 0] [c, 2] [g, 6] [f, 5] [b, 1] [d, 3] [h, 7]
*/

[Nahoře]

concurrent_unordered_set – třída

Třída concurrency::concurrent_unordered_set úzce připomíná třídu concurrent_unordered_map, s tím rozdílem, že spravuje hodnoty místo párů klíčů a hodnot. Třída concurrent_unordered_set neposkytuje operator[] ani metodu at .

Následující příklad ukazuje základní strukturu pro použití concurrent_unordered_set. Tento příklad vloží hodnoty znaků do oblasti ['a', 'i']. Vložení je bezpečné provádět paralelně.

// unordered-set-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_set.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain() 
{
    //
    // Insert a number of items into the set in parallel.

    concurrent_unordered_set<char> set; 

    parallel_for(0, 10000, [&set](int i) {
        set.insert('a' + (i%9)); // Geneate a value in the range [a,i].
    });

    // Print the elements in the set.
    for_each(begin(set), end(set), [](char c) {
        wcout << L"[" << c << L"] ";
    });
}
/* Sample output:
    [e] [i] [a] [c] [g] [f] [b] [d] [h]
*/

[Nahoře]

concurrent_unordered_multiset – třída

Třída concurrency::concurrent_unordered_multiset úzce připomíná concurrent_unordered_set třídu, kromě toho, že umožňuje duplicitní hodnoty. Liší se také od concurrent_unordered_set následujícími způsoby:

Následující příklad ukazuje základní strukturu pro použití concurrent_unordered_multiset. Tento příklad vloží hodnoty znaků do oblasti ['a', 'i']. concurrent_unordered_multiset umožňuje, aby se hodnota vyskytla vícekrát.

// unordered-set-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_set.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain() 
{
    //
    // Insert a number of items into the set in parallel.

    concurrent_unordered_multiset<char> set; 

    parallel_for(0, 40, [&set](int i) {
        set.insert('a' + (i%9)); // Geneate a value in the range [a,i].
    });

    // Print the elements in the set.
    for_each(begin(set), end(set), [](char c) {
        wcout << L"[" << c << L"] ";
    });
}
/* Sample output:
    [e] [e] [e] [e] [i] [i] [i] [i] [a] [a] [a] [a] [a] [c] [c] [c] [c] [c] [g] [g]
    [g] [g] [f] [f] [f] [f] [b] [b] [b] [b] [b] [d] [d] [d] [d] [d] [h] [h] [h] [h]
*/

[Nahoře]

kombinovatelná třída

Třída concurrency::combinable poskytuje opakovaně použitelné, vláknově lokální úložiště, které umožňuje provádět jemné výpočty a následně je sloučit do konečného výsledku. Objekt si můžete představit combinable jako proměnnou redukce.

Třída combinable je užitečná, pokud máte prostředek sdílený mezi několika vlákny nebo úlohami. Třída combinable pomáhá eliminovat sdílený stav tím, že poskytuje přístup ke sdíleným prostředkům způsobem bez uzamčení. Proto tato třída poskytuje alternativu k použití synchronizačního mechanismu, například mutex, k synchronizaci přístupu ke sdíleným datům z více vláken.

Metody a funkce

Následující tabulka uvádí některé z důležitých combinable metod třídy. Další informace o všech combinable metodách třídy naleznete v tématu combinable Class.

metoda Popis
místní Načte odkaz na místní proměnnou přidruženou k aktuálnímu kontextu vlákna.
jasný Odebere z objektu combinable všechny lokální proměnné vlákna.
kombinovat

combine_each
Použije zadanou kombinační funkci k vygenerování konečné hodnoty ze sady všech výpočtů místních vláken.

Třída combinable je třída šablony, která je parametrizována u konečného sloučeného výsledku. Pokud voláte výchozí konstruktor, T typ parametru šablony musí mít výchozí konstruktor a kopírovací konstruktor. T Pokud typ parametru šablony nemá výchozí konstruktor, zavolejte přetíženou verzi konstruktoru, který přebírá inicializační funkci jako jeho parametr.

Po zavolání metod combine nebo combine_each můžete uložit další data do objektu combinable. Metody combine a combine_each můžete volat také vícekrát. Pokud se v objektu combinable nezmění žádná místní hodnota, combine tyto metody combine_each při každém zavolání vytvoří stejný výsledek.

Příklady

Příklady použití combinable třídy najdete v následujících tématech:

[Nahoře]

Postupy: Použití paralelních kontejnerů ke zvýšení účinnosti
Ukazuje, jak používat paralelní kontejnery k efektivnímu ukládání a přístupu k datům paralelně.

Postupy: Použití objektu combinable ke zlepšení výkonu
Ukazuje, jak pomocí combinable třídy eliminovat sdílený stav, a tím zlepšit výkon.

Postupy: Použití objektu combinable ke slučování množin
Ukazuje, jak pomocí combine funkce sloučit místní sady dat z více vláken.

Knihovna PPL (Parallel Patterns Library)
Popisuje PPL, který poskytuje imperativní programovací model, který podporuje škálovatelnost a snadné použití pro vývoj souběžných aplikací.

Odkazy

Třída concurrent_vector

Třída concurrent_queue

Třída concurrent_unordered_map

Třída concurrent_unordered_multimap

Třída concurrent_unordered_set

Třída concurrent_unordered_multiset

kombinovatelná Třída