Równoległe kontenery oraz obiekty

Biblioteka wzorców równoległych (PPL) zawiera kilka kontenerów i obiektów, które zapewniają bezpieczny wątkowo dostęp do ich elementów.

Kontener współbieżny zapewnia bezpieczny dostęp współbieżności do najważniejszych operacji. W tym miejscu wskaźniki lub iteratory są zawsze prawidłowe. Nie jest to gwarancja inicjowania elementów ani określonej kolejności przechodzenia. Funkcjonalność tych kontenerów przypomina te, które są dostarczane przez bibliotekę standardową języka C++. Na przykład klasa concurrency::concurrent_vector przypomina klasę std::vector, z tą różnicą, że concurrent_vector klasa umożliwia równoległe dołączanie elementów. Używaj współbieżnych kontenerów, gdy masz kod równoległy, który wymaga zarówno dostępu do odczytu, jak i zapisu do tego samego kontenera.

Współbieżny obiekt jest współużytkowany współbieżnie między składnikami. Proces, który oblicza stan współbieżnego obiektu równolegle, daje taki sam wynik jak inny proces, który oblicza ten sam stan szeregowo. Współbieżność ::combinable , klasa jest jednym z przykładów współbieżnego typu obiektu. Klasa combinable umożliwia równoległe wykonywanie obliczeń, a następnie łączenie tych obliczeń w końcowy wynik. Użyj obiektów współbieżnych, jeśli w przeciwnym razie użyjesz mechanizmu synchronizacji, na przykład mutexu, aby zsynchronizować dostęp do współużytkowanej zmiennej lub zasobu.

Sekcje

W tym temacie opisano szczegółowo następujące kontenery równoległe i obiekty.

Kontenery współbieżne:

Współbieżne obiekty:

concurrent_vector — Klasa

Klasa concurrency::concurrent_vector to klasa kontenera sekwencji, która podobnie jak klasa std::vector umożliwia losowy dostęp do jego elementów. Klasa concurrent_vector umożliwia bezpieczne dołączanie współbieżności i operacje dostępu do elementów. Operacje dołączania nie unieważniają istniejących wskaźników ani iteratorów. Operacje dostępu iteracyjnego i przechodzenia są również bezpieczne ze współbieżnością. W tym miejscu wskaźniki lub iteratory są zawsze prawidłowe. Nie jest to gwarancja inicjowania elementów ani określonej kolejności przechodzenia.

Różnice między concurrent_vector a wektorem

Klasa concurrent_vector jest ściśle podobna do vector klasy. Złożoność operacji dostępu dołączania, elementu i iteratora w concurrent_vector obiekcie jest taka sama jak w przypadku vector obiektu. Poniższe punkty ilustrują różnice concurrent_vector między vectorelementami :

  • Dołączanie, dostęp do elementów, dostęp iteracyjny i operacje przechodzenia iteratora concurrent_vector na obiekcie są bezpieczne ze współbieżnością.

  • Elementy można dodawać tylko na końcu concurrent_vector obiektu. Klasa concurrent_vector nie udostępnia insert metody .

  • Obiekt concurrent_vector nie używa semantyki przenoszenia podczas dołączania do niego.

  • Klasa concurrent_vector nie udostępnia erase metod lub pop_back . Podobnie jak w przypadku vectormetody , użyj metody clear , aby usunąć wszystkie elementy z concurrent_vector obiektu.

  • Klasa concurrent_vector nie przechowuje swoich elementów w pamięci. W związku z tym nie można używać concurrent_vector klasy na wszystkie sposoby, które można użyć tablicy. Na przykład w przypadku zmiennej o nazwie v typu concurrent_vectorwyrażenie &v[0]+2 generuje niezdefiniowane zachowanie.

  • Klasa concurrent_vector definiuje metody grow_by i grow_to_at_least . Te metody przypominają metodę zmiany rozmiaru, z tą różnicą, że są bezpieczne ze współbieżnością.

  • Obiekt concurrent_vector nie przenosi swoich elementów podczas dołączania do niego ani zmienia jego rozmiaru. Dzięki temu istniejące wskaźniki i iteratory pozostają prawidłowe podczas operacji współbieżnych.

  • Środowisko uruchomieniowe nie definiuje wyspecjalizowanej wersji concurrent_vector typu bool.

Operacje współbieżności Sejf

Wszystkie metody dołączane do obiektu lub zwiększające ich rozmiar concurrent_vector lub uzyskują dostęp do elementu w concurrent_vector obiekcie, są bezpieczne dla współbieżności. W tym miejscu wskaźniki lub iteratory są zawsze prawidłowe. Nie jest to gwarancja inicjowania elementów ani określonej kolejności przechodzenia. Wyjątkiem od tej reguły jest resize metoda .

W poniższej tabeli przedstawiono typowe concurrent_vector metody i operatory, które są bezpieczne ze współbieżnością.

Operacje zapewniane przez środowisko uruchomieniowe pod kątem zgodności ze standardową biblioteką języka C++, na przykład reserve, nie są bezpieczne dla współbieżności. W poniższej tabeli przedstawiono typowe metody i operatory, które nie są bezpieczne ze współbieżnością.

Operacje modyfikujące wartość istniejących elementów nie są bezpieczne ze współbieżnością. Użyj obiektu synchronizacji, takiego jak obiekt reader_writer_lock , aby zsynchronizować współbieżne operacje odczytu i zapisu do tego samego elementu danych. Aby uzyskać więcej informacji na temat obiektów synchronizacji, zobacz Struktury danych synchronizacji.

Podczas konwertowania istniejącego kodu używanego vector do użycia concurrent_vectoroperacji współbieżnych może spowodować zmianę zachowania aplikacji. Rozważmy na przykład następujący program, który współbieżnie wykonuje dwa zadania na concurrent_vector obiekcie. Pierwsze zadanie dołącza dodatkowe elementy do concurrent_vector obiektu. Drugie zadanie oblicza sumę wszystkich elementów w tym samym obiekcie.

// 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 Mimo że metoda jest bezpieczna współbieżnością, współbieżne wywołanie metody push_back powoduje zmianę wartości zwracanej przez end metodę . Liczba elementów przechodzenia iteratora jest nieokreślona. W związku z tym ten program może wygenerować inny wynik za każdym razem, gdy go uruchomisz. Gdy typ elementu jest nietrywialny, możliwe jest istnienie warunku wyścigu między wywołaniami push_back i end . Metoda end może zwrócić przydzielony element, ale nie w pełni zainicjowany.

Wyjątek Sejf ty

Jeśli operacja wzrostu lub przypisania zgłasza wyjątek, stan concurrent_vector obiektu staje się nieprawidłowy. Zachowanie concurrent_vector obiektu, który jest w nieprawidłowym stanie, jest niezdefiniowane, chyba że określono inaczej. Jednak destruktor zawsze zwalnia pamięć przydzielaną przez obiekt, nawet jeśli obiekt jest w nieprawidłowym stanie.

Typ danych elementów wektorów musi Tspełniać następujące wymagania. W przeciwnym razie zachowanie concurrent_vector klasy jest niezdefiniowane.

  • Destruktor nie może rzucać.

  • Jeśli zgłasza domyślny lub kopiowany konstruktor, destruktor nie może być zadeklarowany za pomocą słowa kluczowego virtual i musi działać poprawnie z pamięcią zerową.

[Top]

concurrent_queue — Klasa

Klasa concurrency::concurrent_queue , podobnie jak klasa std::queue , umożliwia dostęp do jej elementów przednich i tylnych. Klasa concurrent_queue umożliwia wykonywanie operacji kolejki i dequeue w trybie współbieżności. W tym miejscu wskaźniki lub iteratory są zawsze prawidłowe. Nie jest to gwarancja inicjowania elementów ani określonej kolejności przechodzenia. Klasa concurrent_queue zapewnia również obsługę iteratora, która nie jest bezpieczna współbieżności.

Różnice między concurrent_queue i kolejką

Klasa concurrent_queue jest ściśle podobna do queue klasy. Poniższe punkty ilustrują różnice concurrent_queue między queueelementami :

  • Operacje kolejki i dequeue na concurrent_queue obiekcie są bezpieczne ze współbieżnością.

  • Klasa concurrent_queue zapewnia obsługę iteratora, która nie jest bezpieczna współbieżności.

  • Klasa concurrent_queue nie udostępnia front metod lub pop . Klasa concurrent_queue zastępuje te metody, definiując metodę try_pop .

  • Klasa concurrent_queue nie udostępnia back metody . W związku z tym nie można odwołać się do końca kolejki.

  • Klasa concurrent_queue udostępnia metodę unsafe_size zamiast size metody . Metoda unsafe_size nie jest bezpieczna współbieżności.

Operacje współbieżności Sejf

Wszystkie metody w kolejce do lub dequeue z concurrent_queue obiektu są bezpieczne ze współbieżnością. W tym miejscu wskaźniki lub iteratory są zawsze prawidłowe. Nie jest to gwarancja inicjowania elementów ani określonej kolejności przechodzenia.

W poniższej tabeli przedstawiono typowe concurrent_queue metody i operatory, które są bezpieczne ze współbieżnością.

empty Mimo że metoda jest bezpieczna współbieżnością, równoczesna operacja może spowodować wzrost lub zmniejszenie kolejki przed zwróceniem empty metody.

W poniższej tabeli przedstawiono typowe metody i operatory, które nie są bezpieczne ze współbieżnością.

Obsługa iteratora

Zapewnia concurrent_queue iteratory, które nie są bezpieczne ze współbieżnością. Zalecamy używanie tych iteratorów tylko do debugowania.

concurrent_queue Iterator przechodzi tylko w kierunku przodu. W poniższej tabeli przedstawiono operatory obsługiwane przez poszczególne iteratory.

Operator opis
operator++ Przechodzi do następnego elementu w kolejce. Ten operator jest przeciążony w celu zapewnienia semantyki przyrostowej i po przyrostowej.
operator* Pobiera odwołanie do bieżącego elementu.
operator-> Pobiera wskaźnik do bieżącego elementu.

[Top]

concurrent_unordered_map — Klasa

Klasa concurrency::concurrent_unordered_map jest klasą kontenera kojarzącą, która podobnie jak klasa std::unordered_map kontroluje różną długość sekwencji elementów typu std::p air<const Key, Ty>. Mapę nieurządkowaną można traktować jako słownik, do której można dodać parę klucz i wartość lub wyszukać wartość według klucza. Ta klasa jest przydatna, gdy masz wiele wątków lub zadań, które muszą jednocześnie uzyskiwać dostęp do udostępnionego kontenera, wstawiać do niego lub aktualizować.

W poniższym przykładzie przedstawiono podstawową strukturę używania programu concurrent_unordered_map. Ten przykład wstawia klucze znaków w zakresie ['a', 'i']. Ponieważ kolejność operacji jest niezdefiniowana, ostateczna wartość każdego klucza jest również niezdefiniowana. Jednak bezpieczne jest równoległe wykonywanie wstawiania.

// 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]
*/

Aby zapoznać się z przykładem używanym concurrent_unordered_map do równoległego wykonywania operacji mapowania i redukcji, zobacz Jak wykonywać operacje mapowania i redukcji równolegle.

Różnice między concurrent_unordered_map i unordered_map

Klasa concurrent_unordered_map jest ściśle podobna do unordered_map klasy. Poniższe punkty ilustrują różnice concurrent_unordered_map między unordered_mapelementami :

  • Metody erase, bucket, bucket_counti bucket_size mają odpowiednio nazwy unsafe_erase, unsafe_bucket, unsafe_bucket_counti unsafe_bucket_size. Konwencja unsafe_ nazewnictwa wskazuje, że te metody nie są bezpieczne ze współbieżnością. Aby uzyskać więcej informacji na temat bezpieczeństwa współbieżności, zobacz Operacje współbieżności Sejf.

  • Operacje wstawiania nie unieważniają istniejących wskaźników ani iteratorów ani nie zmieniają kolejności elementów, które już istnieją na mapie. Operacje wstawiania i przechodzenia mogą wystąpić współbieżnie.

  • concurrent_unordered_map obsługuje tylko iterację przesyłania dalej.

  • Wstawienie nie unieważnia ani nie aktualizuje iteratorów zwracanych przez equal_rangeelement . Wstawianie może dołączać nierówne elementy na końcu zakresu. Iterator początkowy wskazuje równy element.

Aby uniknąć zakleszczenia, żadna metoda blokady concurrent_unordered_map nie jest przechowywana, gdy wywołuje alokator pamięci, funkcje skrótu lub inny kod zdefiniowany przez użytkownika. Ponadto należy upewnić się, że funkcja skrótu zawsze ocenia równe klucze do tej samej wartości. Najlepsze funkcje skrótu równomiernie dystrybuują klucze w przestrzeni kodu skrótu.

Operacje współbieżności Sejf

Klasa concurrent_unordered_map umożliwia bezpieczne wstawianie współbieżności i operacje dostępu do elementów. Operacje wstawiania nie unieważniają istniejących wskaźników ani iteratorów. Operacje dostępu iteracyjnego i przechodzenia są również bezpieczne ze współbieżnością. W tym miejscu wskaźniki lub iteratory są zawsze prawidłowe. Nie jest to gwarancja inicjowania elementów ani określonej kolejności przechodzenia. W poniższej tabeli przedstawiono powszechnie używane concurrent_unordered_map metody i operatory, które są bezpieczne ze współbieżnością.

count Mimo że metoda może być wywoływana bezpiecznie z równoczesnych uruchomionych wątków, różne wątki mogą odbierać różne wyniki, jeśli nowa wartość jest jednocześnie wstawiana do kontenera.

W poniższej tabeli przedstawiono powszechnie używane metody i operatory, które nie są bezpieczne ze współbieżnością.

Oprócz tych metod każda metoda rozpoczynająca się od unsafe_ nie jest również bezpieczna współbieżności.

[Top]

concurrent_unordered_multimap — Klasa

Klasa concurrency::concurrent_unordered_multimap jest ściśle podobna concurrent_unordered_map do klasy, z tą różnicą, że umożliwia mapowania wielu wartości na ten sam klucz. Różni się on również od concurrent_unordered_map następujących sposobów:

W poniższym przykładzie przedstawiono podstawową strukturę używania programu concurrent_unordered_multimap. Ten przykład wstawia klucze znaków w zakresie ['a', 'i']. concurrent_unordered_multimap umożliwia kluczowi posiadanie wielu wartości.

// 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]
*/

[Top]

concurrent_unordered_set — Klasa

Klasa concurrency::concurrent_unordered_set jest ściśle podobna concurrent_unordered_map do klasy, z tą różnicą, że zarządza wartościami zamiast par klucz i wartość. Klasa concurrent_unordered_set nie udostępnia operator[] ani at metody .

W poniższym przykładzie przedstawiono podstawową strukturę używania programu concurrent_unordered_set. W tym przykładzie wstawia wartości znaków w zakresie ['a', 'i']. Równoczesne wykonywanie wstawiania jest bezpieczne.

// 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]
*/

[Top]

concurrent_unordered_multiset — Klasa

Klasa concurrency::concurrent_unordered_multiset jest ściśle podobna concurrent_unordered_set do klasy, z tą różnicą, że zezwala na zduplikowane wartości. Różni się on również od concurrent_unordered_set następujących sposobów:

W poniższym przykładzie przedstawiono podstawową strukturę używania programu concurrent_unordered_multiset. W tym przykładzie wstawia wartości znaków w zakresie ['a', 'i']. concurrent_unordered_multiset umożliwia wielokrotne wystąpienie wartości.

// 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]
*/

[Top]

combinable — Klasa

Klasa concurrency::combinable udostępnia magazyn wielokrotnego użytku, wątkowo-lokalny, który umożliwia wykonywanie precyzyjnych obliczeń, a następnie scalanie tych obliczeń w końcowy wynik. Obiekt można traktować combinable jako zmienną redukcji.

Klasa jest przydatna combinable , gdy masz zasób, który jest współużytkowany między kilkoma wątkami lub zadaniami. Klasa combinable pomaga wyeliminować stan udostępniony, zapewniając dostęp do zasobów udostępnionych w sposób wolny od blokady. W związku z tym ta klasa stanowi alternatywę dla korzystania z mechanizmu synchronizacji, na przykład mutexu, do synchronizowania dostępu do udostępnionych danych z wielu wątków.

Metody i funkcje

W poniższej tabeli przedstawiono niektóre ważne metody combinable klasy. Aby uzyskać więcej informacji na temat wszystkich combinable metod klas, zobacz łączenie klasy.

Metoda opis
local Pobiera odwołanie do zmiennej lokalnej skojarzonej z bieżącym kontekstem wątku.
Wyczyść Usuwa wszystkie zmienne lokalne wątku combinable z obiektu.
Połączyć

combine_each
Używa udostępnionej funkcji łączenia, aby wygenerować ostateczną wartość z zestawu wszystkich obliczeń lokalnych wątków.

Klasa combinable jest klasą szablonu sparametryzowaną w końcowym scalanym wyniku. W przypadku wywołania konstruktora T domyślnego typ parametru szablonu musi mieć konstruktor domyślny i konstruktor kopiujący. T Jeśli typ parametru szablonu nie ma konstruktora domyślnego, wywołaj przeciążoną wersję konstruktora, który przyjmuje funkcję inicjowania jako parametr.

Dodatkowe dane można przechowywać w combinable obiekcie po wywołaniu metody łączenia lub combine_each . Metody i combine_each można również wywołać combine wiele razy. Jeśli w obiekcie nie zmienia się żadna wartość lokalna combinable , combine metody i combine_each generują ten sam wynik za każdym razem, gdy są wywoływane.

Przykłady

Przykłady dotyczące korzystania z combinable klasy można znaleźć w następujących tematach:

[Top]

Instrukcje: korzystanie z kontenerów równoległych do zwiększania wydajności
Pokazuje, jak używać kontenerów równoległych do wydajnego przechowywania danych i uzyskiwania do nich dostępu równolegle.

Instrukcje: korzystanie z wyników połączonych do poprawiania wydajności
Pokazuje, jak za pomocą combinable klasy wyeliminować stan udostępniony, a tym samym zwiększyć wydajność.

Instrukcje: korzystanie z wyników połączonych w celu łączenia zestawów
Pokazuje, jak używać combine funkcji do scalania zestawów danych wątkowych lokalnych.

Biblioteka równoległych wzorców (PLL)
Opisuje PPL, który zapewnia imperatywnego modelu programowania, który promuje skalowalność i łatwość użycia do tworzenia współbieżnych aplikacji.

Odwołanie

concurrent_vector, klasa

concurrent_queue, klasa

concurrent_unordered_map, klasa

concurrent_unordered_multimap, klasa

concurrent_unordered_set, klasa

concurrent_unordered_multiset, klasa

combinable, klasa