Dela via


Parallella containrar och objekt

PPL (Parallel Patterns Library) innehåller flera containrar och objekt som ger trådsäker åtkomst till elementen.

En samtidig container ger samtidig åtkomst till de viktigaste åtgärderna. Här innebär samtidighetssäkert att pekare eller iteratorer alltid är giltiga. Det är inte en garanti för elementinitiering eller en viss bläddreringsordning. Funktionerna i dessa containrar liknar de som tillhandahålls av C++-standardbiblioteket. Till exempel liknar concurrency::concurrent_vector-klassen std::vector-klassen, förutom att concurrent_vector-klassen tillåter dig att addera element parallellt. Använd samtidiga containrar när du har parallell kod som kräver både läs- och skrivåtkomst till samma container.

Ett samtidigt objekt delas samtidigt mellan komponenter. En process som beräknar tillståndet för ett parallellt objekt ger samma resultat som en annan process som beräknar samma tillstånd seriellt. Klassen concurrency::combinable är ett exempel på en samtidig objekttyp. Med combinable klassen kan du utföra beräkningar parallellt och sedan kombinera dessa beräkningar till ett slutligt resultat. Använd samtidiga objekt när du annars skulle använda en synkroniseringsmekanism, till exempel en mutex, för att synkronisera åtkomst till en delad variabel eller resurs.

Sektioner

I det här avsnittet beskrivs följande parallella containrar och objekt i detalj.

Samtidiga containrar:

Samtidiga objekt:

concurrent_vector-klass

Klassen concurrency::concurrent_vector är en sekvenscontainerklass som precis som klassen std::vector ger dig slumpmässig åtkomst till dess element. Klassen concurrent_vector möjliggör samtidighetssäkra tilläggs- och elementåtkomståtgärder. Tilläggsåtgärder ogiltigförklarar inte befintliga pekare eller iteratorer. Iteratoråtkomst och traverseringsoperationer är också samtidighetssäkra. Här innebär samtidighetssäkert att pekare eller iteratorer alltid är giltiga. Det är inte en garanti för elementinitiering eller en viss bläddreringsordning.

Skillnader mellan concurrent_vector och vektor

Klassen concurrent_vector liknar klassen vector. Komplexiteten i tilläggs-, elementåtkomst- och iteratoråtkomståtgärder på ett concurrent_vector objekt är samma som för ett vector objekt. Följande punkter illustrerar var concurrent_vector skiljer sig från vector:

  • Tillägg, elementåtkomst, iteratoråtkomst och iteratortraverseringar på ett concurrent_vector-objekt är säkra för samtidighet.

  • Du kan bara lägga till element i slutet av ett concurrent_vector objekt. Klassen concurrent_vector tillhandahåller inte metoden insert.

  • Ett concurrent_vector objekt använder inte rörelse-semantik när du lägger till det.

  • Klassen concurrent_vector tillhandahåller inte metoderna erase eller pop_back. Precis som med vectoranvänder du clear-metoden för att ta bort alla element från ett concurrent_vector objekt.

  • Klassen concurrent_vector lagrar inte elementen sammanhängande i minnet. Därför kan du inte använda concurrent_vector klassen på alla sätt som du kan använda en matris. För en variabel med namnet v av typen concurrent_vectorskapar uttrycket &v[0]+2 till exempel odefinierat beteende.

  • Klassen concurrent_vector definierar metoderna grow_by och grow_to_at_least . Dessa metoder liknar storleksändringsmetoden , förutom att de är samtidighetssäkra.

  • Ett concurrent_vector objekt flyttar inte dess element när du lägger till det eller ändrar storlek på det. På så sätt kan befintliga pekare och iteratorer förbli giltiga under samtidiga åtgärder.

  • Körtiden definierar inte en specialiserad version av concurrent_vector för typen bool.

Concurrency-Safe åtgärder

Alla metoder som lägger till eller ökar storleken på ett concurrent_vector objekt, eller som har åtkomst till ett element i ett concurrent_vector objekt, är samtidighetssäkra. Här innebär samtidighetssäkert att pekare eller iteratorer alltid är giltiga. Det är inte en garanti för elementinitiering eller en viss bläddreringsordning. Undantaget till den här regeln är resize metoden.

I följande tabell visas vanliga concurrent_vector metoder och operatorer som är samtidighetssäkra.

Operationer som tillhandahålls av runtime-miljön för kompatibilitet med C++-standardbiblioteket, till exempel reserve, är inte trådsäkra. I följande tabell visas vanliga metoder och operatorer som inte är samtidighetssäkra.

Åtgärder som ändrar värdet för befintliga element är inte samtidighetssäkra. Använd ett synkroniseringsobjekt, till exempel ett reader_writer_lock-objekt för att synkronisera samtidiga läs- och skrivåtgärder till samma dataelement. Mer information om synkroniseringsobjekt finns i Synkronisera datastrukturer.

När du konverterar befintlig kod som använder vector för att använda concurrent_vectorkan samtidiga åtgärder göra att programmets beteende ändras. Tänk dig till exempel följande program som samtidigt utför två uppgifter på ett concurrent_vector objekt. Den första uppgiften lägger till ytterligare element i ett concurrent_vector objekt. Den andra aktiviteten beräknar summan av alla element i samma objekt.

// 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 Även om metoden är samtidighetssäker, gör ett samtidigt anrop till push_back-metoden att värdet som returneras av end ändras. Antalet element som iteratorn passerar är obestämt. Därför kan det här programmet generera olika resultat varje gång du kör det. När elementtypen inte är trivial är det möjligt att ett loppvillkor finns mellan push_back och end-anropen. Metoden end kan returnera ett element som är allokerat, men inte helt initierat.

Undantagssäkerhet

Om en tillväxt- eller tilldelningsåtgärd utlöser ett undantag blir objektets concurrent_vector tillstånd ogiltigt. Beteendet för ett concurrent_vector objekt som är i ett ogiltigt tillstånd är odefinierat om inget annat anges. Destruktorn frigör dock alltid det minne som objektet allokerar, även om objektet är i ett ogiltigt tillstånd.

Datatypen för vektorelementen, T, måste uppfylla följande krav. Annars är klassens concurrent_vector beteende odefinierat.

  • Destruktorn får inte kasta.

  • Om standardkonstruktorn eller kopieringskonstruktorn genererar får destruktorn inte deklareras med nyckelordet virtual och den måste fungera korrekt med nollinitierat minne.

[Topp]

concurrent_queue-klass

Med klassen concurrency::concurrent_queue , precis som klassen std::queue , kan du komma åt dess element på framsidan och baksidan. Klassen concurrent_queue möjliggör samtidighetssäkra queue- och dequeue-åtgärder. Här innebär samtidighetssäkert att pekare eller iteratorer alltid är giltiga. Det är inte en garanti för elementinitiering eller en viss bläddreringsordning. Klassen concurrent_queue tillhandahåller även iteratorstöd som inte är samtidighetssäkert.

Skillnader mellan concurrent_queue och queue

Klassen concurrent_queue liknar klassen queue. Följande punkter illustrerar var concurrent_queue skiljer sig från queue:

  • Enqueue- och dequeue-åtgärder på ett concurrent_queue objekt är samtidighetssäkra.

  • Klassen concurrent_queue tillhandahåller iteratorstöd som inte är samtidighetssäkert.

  • Klassen concurrent_queue tillhandahåller inte metoderna front eller pop. Klassen concurrent_queue ersätter dessa metoder genom att definiera metoden try_pop .

  • Klassen concurrent_queue tillhandahåller inte metoden back. Därför kan du inte referera till slutet av kön.

  • Klassen concurrent_queue innehåller metoden unsafe_size i stället för size metoden. Metoden unsafe_size är inte samtidighetssäker.

Concurrency-Safe åtgärder

Alla metoder som köar till eller avviker från ett concurrent_queue objekt är samtidighetssäkra. Här innebär samtidighetssäkert att pekare eller iteratorer alltid är giltiga. Det är inte en garanti för elementinitiering eller en viss bläddreringsordning.

I följande tabell visas vanliga concurrent_queue metoder och operatorer som är samtidighetssäkra.

Även om metoden empty är samtidighetssäker kan en samtidig åtgärd leda till att kön växer eller krymper innan metoden empty returnerar.

I följande tabell visas vanliga metoder och operatorer som inte är samtidighetssäkra.

Iteratorstöd

Tillhandahåller concurrent_queue iteratorer som inte är samtidighetssäkra. Vi rekommenderar att du endast använder dessa iteratorer för felsökning.

En concurrent_queue iterator passerar endast element i framåtriktad riktning. I följande tabell visas de operatorer som varje iterator stöder.

Operatör Beskrivning
operator++ Avancerar till nästa objekt i kön. Den här operatorn är överbelastad för att ge både pre-increment och post-increment semantik.
operator* Hämtar en referens till det aktuella objektet.
operator-> Hämtar en pekare till det aktuella objektet.

[Topp]

concurrent_unordered_map-klass

Klassen concurrency::concurrent_unordered_map är en associativ containerklass som precis som klassen std::unordered_map styr en sekvens med varierande längd av element av typen std::pair<const Key, Ty>. Tänk på en oordnad karta som en ordlista som du kan lägga till ett nyckel- och värdepar till eller slå upp ett värde efter nyckel. Den här klassen är användbar när du har flera trådar eller uppgifter som måste ha samtidig åtkomst till en delad container, infoga i den eller uppdatera den.

I följande exempel visas den grundläggande strukturen för att använda concurrent_unordered_map. I det här exemplet infogas teckennycklar i intervallet ['a', 'i']. Eftersom ordningen på åtgärderna är obestämd är det slutliga värdet för varje nyckel också obestämt. Det är dock säkert att utföra infogningar parallellt.

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

Ett exempel som använder concurrent_unordered_map för att utföra en mappnings- och reduce-åtgärd parallellt finns i How to: Perform Map and Reduce Operations in Parallel (Så här utför du map- och reduce-åtgärder parallellt).

Skillnader mellan concurrent_unordered_map och unordered_map

Klassen concurrent_unordered_map liknar klassen unordered_map. Följande punkter illustrerar var concurrent_unordered_map skiljer sig från unordered_map:

  • Metoderna erase, bucket, bucket_count, och bucket_size heter unsafe_erase, unsafe_bucket, unsafe_bucket_countoch unsafe_bucket_sizerespektive . Namngivningskonventionen unsafe_ anger att dessa metoder inte är samtidighetssäkra. Mer information om samtidighetssäkerhet finns iConcurrency-Safe Åtgärder.

  • Infogningsåtgärder ogiltigförklarar inte befintliga pekare eller iteratorer och ändrar inte heller ordningen på objekt som redan finns på kartan. Infognings- och blädderingsåtgärder kan utföras samtidigt.

  • concurrent_unordered_map stöder endast framlängesiteration.

  • Infogningen ogiltigförklarar eller uppdaterar inte de iteratorer som returneras av equal_range. Infogning kan lägga till ojämna objekt i slutet av intervallet. Iteratorn begin pekar på ett lika med objekt.

För att undvika dödlägen finns det ingen metod för concurrent_unordered_map att hålla ett lås när den anropar minnesallokeraren, hashfunktionerna eller annan användardefinierad kod. Du måste också se till att hash-funktionen alltid utvärderar lika med nycklar till samma värde. De bästa hash-funktionerna distribuerar nycklar jämnt över hash-kodutrymmet.

Concurrency-Safe åtgärder

Klassen concurrent_unordered_map möjliggör samtidighetssäkra infognings- och elementåtkomståtgärder. Infogningsåtgärder ogiltigförklarar inte befintliga pekare eller iteratorer. Iteratoråtkomst och traverseringsoperationer är också samtidighetssäkra. Här innebär samtidighetssäkert att pekare eller iteratorer alltid är giltiga. Det är inte en garanti för elementinitiering eller en viss bläddreringsordning. I följande tabell visas de metoder och operatorer som används concurrent_unordered_map ofta och som är samtidighetssäkra.

count Även om metoden kan anropas på ett säkert sätt från trådar som körs samtidigt kan olika trådar få olika resultat om ett nytt värde infogas samtidigt i containern.

I följande tabell visas de metoder och operatorer som används ofta och som inte är samtidighetssäkra.

Förutom dessa metoder är alla metoder som börjar med unsafe_ inte heller samtidighetssäkra.

[Topp]

concurrent_unordered_multimap-klass

Concurrency::concurrent_unordered_multimap-klassen liknar concurrent_unordered_map-klassen, men tillåter att flera värden kan mappas till samma nyckel. Det skiljer sig också från concurrent_unordered_map på följande sätt:

  • Metoden concurrent_unordered_multimap::insert returnerar en iterator i stället std::pair<iterator, bool>för .

  • Klassen concurrent_unordered_multimap tillhandahåller varken operator[] eller at-metoden.

I följande exempel visas den grundläggande strukturen för att använda concurrent_unordered_multimap. I det här exemplet infogas teckennycklar i intervallet ['a', 'i']. concurrent_unordered_multimap gör det möjligt för en nyckel att ha flera värden.

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

[Topp]

concurrent_unordered_set-klass

Concurrency::concurrent_unordered_set-klassen liknar concurrent_unordered_map-klassen, förutom att den hanterar värden i stället för nyckel- och värdepar. Klassen concurrent_unordered_set tillhandahåller varken operator[] eller at-metoden.

I följande exempel visas den grundläggande strukturen för att använda concurrent_unordered_set. I det här exemplet infogas teckenvärden i intervallet ['a', 'i']. Det är säkert att utföra infogningar parallellt.

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

[Topp]

concurrent_unordered_multiset-klass

Concurrency::concurrent_unordered_multiset-klassen liknar concurrent_unordered_set-klassen, förutom att den tillåter duplicerade värden. Det skiljer sig också från concurrent_unordered_set på följande sätt:

  • Metoden concurrent_unordered_multiset::insert returnerar en iterator i stället för std::pair<iterator, bool>.

  • Klassen concurrent_unordered_multiset tillhandahåller varken operator[] eller at-metoden.

I följande exempel visas den grundläggande strukturen för att använda concurrent_unordered_multiset. I det här exemplet infogas teckenvärden i intervallet ['a', 'i']. concurrent_unordered_multiset aktiverar ett värde för att inträffa flera gånger.

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

[Topp]

kombinerbar klass

Klassen concurrency::combinable ger återanvändbar, trådlokal lagring som gör att du kan utföra detaljerade beräkningar och sedan sammanfoga dessa beräkningar till ett slutligt resultat. Du kan se ett combinable objekt som en minskningsvariabel.

Klassen combinable är användbar när du har en resurs som delas mellan flera trådar eller aktiviteter. Klassen combinable hjälper dig att eliminera delat tillstånd genom att ge åtkomst till delade resurser på ett låsfritt sätt. Därför är den här klassen ett alternativ till att använda en synkroniseringsmekanism, till exempel en mutex, för att synkronisera åtkomst till delade data från flera trådar.

Metoder och funktioner

I följande tabell visas några av de viktiga metoderna i combinable klassen. Mer information om alla klassmetoder finns i combinablekombinerbar klass.

Metod Beskrivning
lokala Hämtar en referens till den lokala variabeln som är associerad med den aktuella trådkontexten.
klar Tar bort alla trådlokala variabler från objektet combinable .
kombinera

combine_each
Använder den angivna kombinationsfunktionen för att generera ett slutligt värde från uppsättningen med alla trådlokala beräkningar.

Klassen combinable är en mallklass som parametriseras på det slutliga sammanfogade resultatet. Om du anropar standardkonstruktorn T måste mallparametertypen ha en standardkonstruktor och en kopieringskonstruktor. T Om mallparametertypen inte har någon standardkonstruktor anropar du den överlagrade versionen av konstruktorn som tar en initieringsfunktion som parameter.

Du kan lagra ytterligare data i ett combinable objekt när du anropar metoderna kombinera eller combine_each . Du kan också anropa combine metoderna och combine_each flera gånger. Om inget lokalt värde i ett combinable objekt ändras combine ger metoderna och combine_each samma resultat varje gång de anropas.

Exempel

Exempel på hur du använder combinable klassen finns i följande avsnitt:

[Topp]

Gör så här: Använda parallella containrar för att öka effektiviteten
Visar hur du använder parallella containrar för att effektivt lagra och komma åt data parallellt.

Gör så här: Använd kombinerbart för att förbättra prestanda
Visar hur du använder combinable klassen för att eliminera delat tillstånd och därmed förbättra prestandan.

Gör så här: Använd kombinerbart för att kombinera uppsättningar
Visar hur du använder en combine funktion för att sammanfoga trådlokala datauppsättningar.

PPL (Parallel Patterns Library)
Beskriver PPL, som tillhandahåller en imperativ programmeringsmodell som främjar skalbarhet och användarvänlighet för utveckling av samtidiga program.

Hänvisning

Concurrent_vector-Klass

concurrent_queue-klass

Concurrent_unordered_map-Klass

concurrent_unordered_multimap-klass

Klassen concurrent_unordered_set

concurrent_unordered_multiset-klass

kombinerbar klass