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řídaconcurrent_vector
neposkytuje metoduinsert
.Objekt
concurrent_vector
při připojení k objektu nepoužívá sémantiku přesunutí.Třída
concurrent_vector
neposkytujeerase
anipop_back
metody. Stejně jako uvector
, 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žítconcurrent_vector
všemi způsoby, jak lze použít pole. Například pro proměnnou s názvemv
typuconcurrent_vector
vý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 typbool
.
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 kompatibilitu se standardní knihovnou C++, reserve
napří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.
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í T
splň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
neposkytujefront
anipop
metody. Třídaconcurrent_queue
nahrazuje tyto metody definováním try_pop metody.Třída
concurrent_queue
neposkytuje metoduback
. Proto nelze odkazovat na konec fronty.Třída
concurrent_queue
poskytuje unsafe_size metodusize
místo metody. Metodaunsafe_size
není bezpečná pro souběžnost.
Operace bezpečné souběžnosti
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
,bucket
bucket_count
abucket_size
metody jsou pojmenoványunsafe_erase
,unsafe_bucket
unsafe_bucket_count
, aunsafe_bucket_size
, v uvedeném pořadí. Konvenceunsafe_
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í 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 bezpečné souběžnosti
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ů:
Metoda concurrent_unordered_multimap::insert vrátí iterátor místo
std::pair<iterator, bool>
.Třída
concurrent_unordered_multimap
neposkytujeoperator[]
ani metoduat
.
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ů:
Metoda concurrent_unordered_multiset::insert vrátí iterátor místo
std::pair<iterator, bool>
.Třída
concurrent_unordered_multiset
neposkytujeoperator[]
ani metoduat
.
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]
Příbuzná témata
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_unordered_map – třída
concurrent_unordered_multimap – třída
concurrent_unordered_set – třída