Megosztás a következőn keresztül:


Párhuzamos tárolók és objektumok

A párhuzamos minták kódtára (PPL) számos tárolót és objektumot tartalmaz, amelyek szálbiztos hozzáférést biztosítanak az elemeikhez.

Az egyidejű tároló egyidejűleg biztonságos hozzáférést biztosít a legfontosabb műveletekhez. Itt az egyidejűség-biztonságos érték azt jelenti, hogy a mutató vagy az iterátor mindig érvényes. Ez nem garantálja az elemek inicializálását vagy egy adott bejárási sorrendet. Ezeknek a tárolóknak a funkciói a C++ standard kódtár által biztosítottakhoz hasonlóak. Az egyidejűség::concurrent_vector osztály például az std::vector osztályhoz hasonlít, azzal a kivétellel, hogy az concurrent_vector osztály lehetővé teszi az elemek párhuzamos hozzáfűzését. Egyidejű tárolókat akkor használjon, ha olyan párhuzamos kóddal rendelkezik, amely olvasási és írási hozzáférést is igényel ugyanahhoz a tárolóhoz.

Az egyidejű objektumokat a rendszer egyidejűleg osztja meg az összetevők között. Az egyidejű objektumok állapotát párhuzamosan kiszámoló folyamat ugyanazt az eredményt hozza létre, mint egy másik folyamat, amely ugyanazt az állapotot sorosan számítja ki. Az concurrency::combinable osztály az egyidejű objektum típusának egyik példája. Az combinable osztály lehetővé teszi a számítások párhuzamos végrehajtását, majd ezeket a számításokat egy végső eredményben egyesíti. Használjon egyidejű objektumokat, ha egyébként egy szinkronizálási mechanizmust, például egy mutexet használna a megosztott változóhoz vagy erőforráshoz való hozzáférés szinkronizálásához.

Szakaszok

Ez a témakör részletesen ismerteti a következő párhuzamos tárolókat és objektumokat.

Egyidejű tárolók:

Egyidejű objektumok:

concurrent_vector osztály

Az egyidejűség::concurrent_vector osztály egy szekvenciatároló-osztály, amely az std::vector osztályhoz hasonlóan lehetővé teszi az elemek véletlenszerű elérését. Az concurrent_vector osztály lehetővé teszi az egyidejűségmentes hozzáfűzési és elemhozzáférési műveleteket. A hozzáfűzési műveletek nem érvényteleníti a meglévő mutatókat vagy iterátorokat. Az iterátor-hozzáférés és a bejárási műveletek egyidejűleg is biztonságosak. Itt az egyidejűség-biztonságos érték azt jelenti, hogy a mutató vagy az iterátor mindig érvényes. Ez nem garantálja az elemek inicializálását vagy egy adott bejárási sorrendet.

Különbségek a concurrent_vector és a vektor között

Az concurrent_vector osztály nagyon hasonlít az osztályra vector . Az objektumon végzett hozzáfűzési, elemhozzáférési és iterátor-hozzáférési műveletek concurrent_vector összetettsége megegyezik az vector objektuméval. Az alábbi pontok bemutatják, hogy miben concurrent_vector különbözik a következőtől vector:

  • Az concurrent_vector objektum hozzáfűzési, elemhozzáférési, iterátor-hozzáférési és iterátor-bejárási műveletei szálbiztosak.

  • Az concurrent_vector objektumhoz csak a végén adhat elemeket hozzá. Az concurrent_vector osztály nem adja meg a metódust insert .

  • A concurrent_vector objektum nem használ áthelyezési szemantikát, amikor hozzáfűz valamit.

  • Az concurrent_vector osztály nem biztosítja a erase vagy a pop_back metódusokat. vectorA tiszta metódussal az összes elemet eltávolíthatja egy concurrent_vector objektumból.

  • Az concurrent_vector osztály nem tárolja az elemeit egybefüggően a memóriában. Ezért nem használhatja az concurrent_vector osztályt minden olyan módon, amellyel tömböt használhat. Például egy típusnak vnevezett concurrent_vector változó esetében a kifejezés &v[0]+2 nem definiált viselkedést eredményez.

  • Az concurrent_vector osztály határozza meg a grow_by és grow_to_at_least metódusokat. Ezek a metódusok az átméretezési módszerhez hasonlítanak, azzal a kivételével, hogy egyidejűleg biztonságosak.

  • Egy concurrent_vector objektum nem helyezi át az elemeit, amikor hozzáfűzi vagy átméretezi. Ez lehetővé teszi, hogy a meglévő mutatók és iterátorok érvényesek maradjanak az egyidejű műveletek során.

  • A futtatókörnyezet nem definiálja a concurrent_vector típus boolspeciális verzióját.

Concurrency-Safe műveletek

Minden metódus, amely hozzáfűzi vagy növeli egy concurrent_vector objektum méretét, vagy egy objektum egy eleméhez concurrent_vector fér hozzá, egyidejűségbiztos. Itt az egyidejűség-biztonságos érték azt jelenti, hogy a mutató vagy az iterátor mindig érvényes. Ez nem garantálja az elemek inicializálását vagy egy adott bejárási sorrendet. A szabály alól kivételt képez a resize metódus.

Az alábbi táblázat azokat a gyakori concurrent_vector metódusokat és operátorokat mutatja be, amelyek egyidejűleg biztonságosak.

A futtatókörnyezet által a C++ standard könyvtárral való kompatibilitást biztosító műveletek, például reserve, nem szálbiztosak. Az alábbi táblázat azokat a gyakori metódusokat és operátorokat mutatja be, amelyek nem egyidejűleg biztonságosak.

A meglévő elemek értékét módosító műveletek nem szálbiztosak. Egy szinkronizálási objektum, például egy reader_writer_lock objektum használatával szinkronizálhatja az egyidejű olvasási és írási műveleteket ugyanarra az adatelemre. A szinkronizálási objektumokról további információt a Szinkronizálási adatstruktúrák című témakörben talál.

Amikor átalakítja a meglévő kódot, ami vector-ot használ concurrent_vector-re, az egyidejű műveletek az alkalmazás viselkedésének megváltozását okozhatják. Vegyük például az alábbi programot, amely egyszerre két feladatot hajt végre egy concurrent_vector objektumon. Az első tevékenység további elemeket fűz hozzá egy concurrent_vector objektumhoz. A második tevékenység kiszámítja az összes elem összegét ugyanabban az objektumban.

// 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;
      }
   );
}

Bár a end metódus egyidejűleg biztonságos, a push_back metódus egyidejű hívása miatt a visszaadott end érték megváltozik. Az iterátor által bejárt elemek száma meghatározhatatlan. Ezért ez a program minden futtatáskor más eredményt tud eredményezni. Ha az elem típusa nem triviális, lehetséges, hogy versenyfeltétel létezhet a push_back és end hívások között. A end metódus visszaadhat egy lefoglalt, de nem teljesen inicializált elemet.

Kivételbiztonság

Ha egy növekedési vagy hozzárendelési művelet kivételt eredményez, az concurrent_vector objektum állapota érvénytelenné válik. Az érvénytelen állapotú objektumok viselkedése concurrent_vector nincs meghatározva, kivéve, ha másként van megadva. A destruktor azonban mindig felszabadítja az objektum által lefoglalt memóriát, még akkor is, ha az objektum érvénytelen állapotban van.

A vektorelemek Tadattípusának meg kell felelnie az alábbi követelményeknek. Ellenkező esetben az concurrent_vector osztály viselkedése nincs meghatározva.

  • A destruktort nem szabad dobni.

  • Ha az alapértelmezett vagy másolási konstruktor kivételt dob, a dekonstruktor nem deklarálható a virtual kulcsszó használatával, és megfelelően kell működnie nulla inicializált memóriával.

[Felső]

concurrent_queue osztály

Az egyidejűség::concurrent_queue osztály, csakúgy, mint az std::queue osztály, lehetővé teszi annak elülső és hátsó elemeinek elérését. Az concurrent_queue osztály lehetővé teszi az egyidejű műveletek biztonságos végrehajtását a sorba állítás és a sorból kivétel során. Itt az egyidejűség-biztonságos érték azt jelenti, hogy a mutató vagy az iterátor mindig érvényes. Ez nem garantálja az elemek inicializálását vagy egy adott bejárási sorrendet. Az concurrent_queue osztály olyan iterátortámogatást is biztosít, amely nem egyidejűségbiztos.

Különbségek a concurrent_queue és a sor között

Az concurrent_queue osztály nagyon hasonlít az osztályra queue . Az alábbi pontok bemutatják, hogy miben concurrent_queue különbözik a következőtől queue:

  • Az concurrent_queue objektum enqueue és dequeue műveletei szálbiztosak.

  • Az concurrent_queue osztály olyan iterátortámogatást biztosít, amely nem egyidejűségbiztos.

  • Az concurrent_queue osztály nem biztosítja a front vagy a pop metódusokat. Az concurrent_queue osztály ezeket a metódusokat a try_pop metódus definiálásával helyettesíti.

  • Az concurrent_queue osztály nem adja meg a metódust back . Ezért nem hivatkozhat az üzenetsor végére.

  • A concurrent_queue osztály a metódus helyett a size metódust biztosítja. A unsafe_size metódus nem egyidejűségbiztos.

Concurrency-Safe műveletek

Minden olyan metódus, amely egy concurrent_queue objektumra hivatkozik vagy onnan leküldi azokat, az egyidejűség-biztonságos. Itt az egyidejűség-biztonságos érték azt jelenti, hogy a mutató vagy az iterátor mindig érvényes. Ez nem garantálja az elemek inicializálását vagy egy adott bejárási sorrendet.

Az alábbi táblázat azokat a gyakori concurrent_queue metódusokat és operátorokat mutatja be, amelyek egyidejűleg biztonságosak.

Bár a empty metódus egyidejűleg biztonságos, az egyidejű művelet miatt az üzenetsor növekedhet vagy zsugorodhat, mielőtt a empty metódus visszatér.

Az alábbi táblázat azokat a gyakori metódusokat és operátorokat mutatja be, amelyek nem egyidejűleg biztonságosak.

Iterator-támogatás

A concurrent_queue szolgáltatás olyan iterátorokat biztosít, amelyek nem egyidejűleg biztonságosak. Javasoljuk, hogy ezeket az iterátorokat csak hibakereséshez használja.

Az concurrent_queue iterátor csak előrefelé halad az elemek között. Az alábbi táblázat az egyes iterátorok által támogatott operátorokat mutatja be.

Operátor Leírás
operator++ Tovább lép a sorban következő elemre. Ez az operátor azért van túlterhelve, hogy biztosítsa mind az előtti, mind az utáni növekmény szemantikáját.
operator* Az aktuális elemre mutató hivatkozást kér le.
operator-> Az aktuális elemre mutató mutatót kér le.

[Felső]

concurrent_unordered_map osztály

Az egyidejűség::concurrent_unordered_map osztály egy asszociatív tárolóosztály, amely az std::unordered_map osztályhoz hasonlóan az std::pair<const Key, Ty> típusú elemek változó hosszúságú szekvenciáját vezérli. A rendezetlen térképeket szótárként tekintheti, amelyekhez kulcs- és értékpárokat adhat hozzá, vagy kulcs alapján kereshet értékeket. Ez az osztály akkor hasznos, ha több szálat vagy feladatot használ, amelyeknek egyidejűleg hozzá kell férnie egy megosztott tárolóhoz, be kell szúrnia vagy frissítenie kell azt.

Az alábbi példa a használat concurrent_unordered_mapalapvető struktúráját mutatja be. Ez a példa beszúrja a karakterkulcsokat az "a", "i" tartományba. Mivel a műveletek sorrendje meghatározatlan, az egyes kulcsok végső értéke szintén meghatározatlan. A beszúrások azonban biztonságosan elvégezhetők párhuzamosan.

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

Egy térkép és a csökkentési művelet párhuzamos végrehajtására szolgáló concurrent_unordered_map példa : Útmutató: Térkép és csökkentési műveletek párhuzamos végrehajtása.

A concurrent_unordered_map és a unordered_map közötti különbségek

Az concurrent_unordered_map osztály nagyon hasonlít az osztályra unordered_map . Az alábbi pontok bemutatják, hogy miben concurrent_unordered_map különbözik a következőtől unordered_map:

  • A erase, bucket, bucket_count, és bucket_size metódusokat rendre unsafe_erase, unsafe_bucket, unsafe_bucket_count, és unsafe_bucket_size névvel illették. Az unsafe_ elnevezési konvenció azt jelzi, hogy ezek a metódusok nem szálbiztosak. Az egyidejűség biztonságáról további információt azConcurrency-Safe műveletek című témakörben talál.

  • A beszúrási műveletek nem érvénytelenítik a meglévő mutatókat vagy iterátorokat, és nem módosítják a térképen már létező elemek sorrendjét sem. A beszúrási és az átjárási műveletek egyidejűleg is előfordulhatnak.

  • concurrent_unordered_map csak a továbbítási iterációt támogatja.

  • A beszúrás nem érvényteleníti vagy frissíti a visszaadott equal_rangeiterátorokat. A beszúrás nem egyenlő elemeket fűzhet a tartomány végéhez. A kezdő iterátor egy egyenlőségelemre mutat.

A holtpont elkerülése érdekében a concurrent_unordered_map egyetlen metódusa sem tart fenn zárolást, amikor a memóriaelosztót, a kivonatfüggvényeket vagy más felhasználó által definiált kódot hív meg. Emellett gondoskodnia kell arról is, hogy a kivonatoló függvény mindig azonos értékhez azonos kulcsokat értékeljen ki. A legjobb kivonatfüggvények egységesen osztják el a kulcsokat a kivonat kódterületén.

Concurrency-Safe műveletek

Az concurrent_unordered_map osztály lehetővé teszi az egyidejűség-biztonságos beszúrási és elemhozzáférési műveleteket. A beszúrási műveletek nem érvényteleníti a meglévő mutatókat vagy iterátorokat. Az iterátor-hozzáférés és a bejárási műveletek egyidejűleg is biztonságosak. Itt az egyidejűség-biztonságos érték azt jelenti, hogy a mutató vagy az iterátor mindig érvényes. Ez nem garantálja az elemek inicializálását vagy egy adott bejárási sorrendet. Az alábbi táblázat azokat a gyakran használt concurrent_unordered_map metódusokat és operátorokat mutatja be, amelyek egyidejűleg biztonságosak.

Bár a count metódus biztonságosan hívható meg az egyidejűleg futó szálaktól, a különböző szálak eltérő eredményeket kaphatnak, ha egy új érték egyidejűleg van beszúrva a tárolóba.

Az alábbi táblázat azokat a gyakran használt módszereket és operátorokat mutatja be, amelyek nem biztonságosak a párhuzamos végrehajtás szempontjából.

Az említett metódusok mellett minden unsafe_-el kezdődő metódus sem szálbiztos.

[Felső]

concurrent_unordered_multimap osztály

Az egyidejűség::concurrent_unordered_multimap osztály nagyon hasonlít a(z) concurrent_unordered_map osztályra, azzal a kivétellel, hogy több érték is társítható ugyanahhoz a kulcshoz. A concurrent_unordered_map az alábbi módokon is különbözik:

  • A concurrent_unordered_multimap::insert metódus iterátort ad vissza std::pair<iterator, bool> helyett.

  • Az concurrent_unordered_multimap osztály nem adja meg operator[] és nem is a metódust at .

Az alábbi példa a használat concurrent_unordered_multimapalapvető struktúráját mutatja be. Ez a példa beszúrja a karakterkulcsokat az "a", "i" tartományba. concurrent_unordered_multimap lehetővé teszi, hogy egy kulcs több értékkel rendelkezzen.

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

[Felső]

concurrent_unordered_set osztály

Az egyidejűség::concurrent_unordered_set osztály nagyon hasonlít a concurrent_unordered_map osztályra, azzal a különbséggel, hogy kulcs- és értékpárok helyett csak értékeket kezel. Az concurrent_unordered_set osztály nem adja meg operator[] és nem is a metódust at .

Az alábbi példa a használat concurrent_unordered_setalapvető struktúráját mutatja be. Ez a példa karakterértékeket szúr be az "a", "i" tartományba. A beszúrások biztonságosan elvégezhetők párhuzamosan.

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

[Felső]

concurrent_unordered_multiset osztály

Az egyidejűség::concurrent_unordered_multiset osztály nagyon hasonlít az concurrent_unordered_set osztályra, kivéve, hogy duplikált értékeket tesz lehetővé. A concurrent_unordered_set az alábbi módokon is különbözik:

  • A concurrent_unordered_multiset::insert metódus egy iterátort ad vissza std::pair<iterator, bool> helyett.

  • Az concurrent_unordered_multiset osztály nem adja meg operator[] és nem is a metódust at .

Az alábbi példa a használat concurrent_unordered_multisetalapvető struktúráját mutatja be. Ez a példa karakterértékeket szúr be az "a", "i" tartományba. concurrent_unordered_multiset lehetővé teszi egy érték többszöri előfordulásá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]
*/

[Felső]

kombinálható osztály

Az egyidejűség::az kombinálható osztály újrafelhasználható, szálalapú tárolást biztosít, amely lehetővé teszi a részletes számítások elvégzését, majd a számítások végleges eredménybe való egyesítését. Az objektumok redukciós combinable változóként is felfoghatók.

Az combinable osztály akkor hasznos, ha több szál vagy tevékenység között megosztott erőforrással rendelkezik. Az combinable osztály segít megszüntetni a megosztott állapotot azáltal, hogy zárolásmentes hozzáférést biztosít a megosztott erőforrásokhoz. Ez az osztály tehát alternatívát nyújt a szinkronizálási mechanizmus, például egy mutex használata helyett, hogy szinkronizálja a több szálból származó megosztott adatokhoz való hozzáférést.

Metódusok és szolgáltatások

Az alábbi táblázat az osztály néhány fontos metódusát mutatja be combinable . Az összes osztálymódszerről további információt az combinablekombinálható osztály című témakörben talál.

Metódus Leírás
helyi Az aktuális szálkörnyezethez társított helyi változóra mutató hivatkozást kér le.
töröl Eltávolítja az összes szál-helyi változót az combinable objektumból.
kombinál

combine_each
A megadott összevonási függvény használatával végső értéket hoz létre az összes szál-helyi számítás halmazából.

Az combinable osztály egy sablonosztály, amely a végső egyesített eredményre van paraméterezve. Ha meghívja az alapértelmezett konstruktort, a sablonparaméter típusának T alapértelmezett konstruktorsal és másolatkonstruktorsal kell rendelkeznie. Ha a T sablonparaméter típusa nem rendelkezik alapértelmezett konstruktorsal, hívja meg a konstruktor túlterhelt verzióját, amely paraméterként inicializálási függvényt vesz igénybe.

Az combinable vagy combine_each metódus meghívása után további adatokat is tárolhat egy objektumban. A combine és combine_each metódusokat többször is meghívhatja. Ha egy combinable objektumban nem változik helyi érték, a metódusok és combine a combine_each metódusok minden alkalommal ugyanazt az eredményt eredményezik, amikor meghívják őket.

Példák

Az osztály használatára combinable vonatkozó példákért tekintse meg az alábbi témaköröket:

[Felső]

Útmutató: Párhuzamos tárolók használata a hatékonyság növeléséhez
Bemutatja, hogyan használható párhuzamos tárolók az adatok párhuzamos tárolására és elérésére.

Útmutató: Kombinálható eszközök használata a teljesítmény javítása érdekében
Bemutatja, hogyan használható az osztály a combinable megosztott állapot megszüntetésére, és ezáltal a teljesítmény javítására.

Útmutató: Kombinálható készletek kombinálása
Bemutatja, hogyan lehet függvényt combine használni a szál-helyi adatkészletek egyesítése érdekében.

Párhuzamos minták kódtára (PPL)
Ismerteti a PPL-t, amely egy imperatív programozási modellt biztosít, amely elősegíti a skálázhatóságot és a könnyű használatot az egyidejű alkalmazások fejlesztéséhez.

Referenciák

concurrent_vector osztály

concurrent_queue osztály

concurrent_unordered_map osztály

concurrent_unordered_multimap Osztály

concurrent_unordered_set osztály

concurrent_unordered_multiset osztály

kombinálható osztály