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 souběžný přístup k nejdůležitějším operacím bezpečným 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í. 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. Souběžné objekty použijte, pokud byste jinak použili synchronizační mechanismus, například mutex, k 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 – třída

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 souběžnost-bezpečné připojení a přístup k prvkům operace. Operace připojení zneplatní existující ukazatele ani iterátory. Přístup iterátoru a operace procházení jsou také souběžné. 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 při připojení k objektu nepoužívá sémantiku přesunutí.

  • 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 třídu použít 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 souběžnosti Sejf

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 kompatibilitu se standardní knihovnou C++, reservenapříklad , nejsou bezpečné pro souběžnost. 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 I když je metoda souběžná a bezpečná, souběžné volání metody push_back způsobí změnu hodnoty vrácené end změnou. 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ími a end voláním existuje push_back podmínka časování. Metoda end může vrátit prvek, který je přidělen, ale není plně inicializován.

Výjimka Sejf ty

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 souběžnost-safe enqueue a dequeue 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í. Třída concurrent_queue také poskytuje podporu iterátoru, která není bezpečná pro souběžnost.

Rozdíly mezi concurrent_queue a frontou

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

  • Operace fronty a dequeue u concurrent_queue objektu jsou bezpečné pro souběžnost.

  • 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 unsafe_size metodu size místo metody. Metoda unsafe_size není bezpečná pro souběžnost.

Operace souběžnosti Sejf

Všechny metody, které zařadí do fronty nebo odřazení z objektu concurrent_queue , jsou concurrency-safe. 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

Poskytuje concurrent_queue iterátory, které nejsou bezpečné pro souběžnost. 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

Concurrency::concurrent_unordered_map třída je asociativní třída kontejneru, která stejně jako std::unordered_map třída řídí různou délku prvků typu std::p air<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:

  • , erase, bucketbucket_counta bucket_size metody jsou pojmenovány unsafe_erase, unsafe_bucketunsafe_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 najdete v tématu Operace souběžnosti Sejf.

  • Operace vložení neoznačují stávající ukazatele ani iterátory, ani nemění pořadí položek, které již v mapě existují. 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í, není při volání alokátoru paměti, funkcí hash nebo jiného uživatelem definovaného concurrent_unordered_map kódu žádná metoda uzamčení. 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 souběžnosti Sejf

Třída concurrent_unordered_map umožňuje operace souběžnosti bezpečné vložení a přístupu k prvkům. Operace vložení zneplatní existující ukazatele ani iterátory. Přístup iterátoru a operace procházení jsou také souběžné. 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_ na souběžnosti, bezpečná.

[Nahoře]

concurrent_unordered_multimap – třída

Souběžnost ::concurrent_unordered_multimap třída úzce připomíná concurrent_unordered_map třídu s tím rozdílem, že umožňuje mapování více hodnot na stejný klíč. Liší se také od concurrent_unordered_map následujících způsobů:

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

Souběžnost ::concurrent_unordered_set třída úzce připomíná concurrent_unordered_map třídu s tím rozdílem, že spravuje hodnoty místo párů klíč a hodnota. 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

Souběžnost ::concurrent_unordered_multiset třída úzce připomíná concurrent_unordered_set třídu s tím rozdílem, že umožňuje duplicitní hodnoty. Liší se také od concurrent_unordered_set následujících způsobů:

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]

combinable – třída

Concurrency::combinable třída poskytuje opakovaně použitelné úložiště s vlákny místní úložiště, které umožňuje provádět jemně odstupňované výpočty a pak tyto výpočty 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 úkoly. 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
local Načte odkaz na místní proměnnou přidruženou k aktuálnímu kontextu vlákna.
Jasné Odebere z objektu všechny proměnné místní podprocesu combinable .
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 konstruktor kopírování. 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 volání kombinační nebo combine_each metod můžete do objektu combinable uložit další data. Metody a combine_each metody můžete také volat combine 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í.

Reference

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 – třída

combinable – třída