Share via


Parallele Container und Objekte

Die Parallel Patterns Library (PPL) enthält mehrere Container und Objekte, die threadsicheren Zugriff auf ihre Elemente bieten.

Parallele Container bieten parallelitätssicheren Zugriff auf die wichtigsten Vorgänge. Die Funktionalität dieser Container ist mit denen der Standardvorlagenbibliothek (Standard Template Library, STL) vergleichbar. Die Concurrency::concurrent_vector-Klasse ähnelt z. B. der std::vector-Klasse. Der Unterschied besteht darin, dass die concurrent_vector-Klasse das parallele Anfügen von Elementen ermöglicht. Verwenden Sie parallele Container, wenn Sie mit parallelem Code arbeiten, der Lese- und Schreibzugriff auf den gleichen Container erfordert.

Parallele Objekte werden von Komponenten gleichzeitig gemeinsam genutzt. Ein Prozess zur parallelen Berechnung des Zustands eines parallelen Objekts und ein Prozess zur seriellen Berechnung desselben Zustands führen zum gleichen Ergebnis. Die Concurrency::combinable-Klasse ist ein Beispiel für einen parallelen Objekttyp. Mit der combinable-Klasse können Sie Berechnungen parallel ausführen und diese dann in einem Endergebnis kombinieren. Verwenden Sie parallele Objekte, wo Sie andernfalls einen Synchronisierungsmechanismus wie z. B. einen Mutex einsetzen würden, um den Zugriff auf eine freigegebene Variable oder Ressource zu synchronisieren.

Abschnitte

In diesem Thema werden die folgenden parallelen Container und Objekte im Detail beschrieben.

Parallele Container:

  • concurrent_vector-Klasse

  • concurrent_queue-Klasse

Parallele Objekte:

  • combinable-Klasse

concurrent_vector-Klasse

Bei der Concurrency::concurrent_vector-Klasse handelt es sich um eine Sequenzcontainerklasse, die wie die std::vector-Klasse wahlfreien Zugriff auf ihre Elemente bietet. Die concurrent_vector-Klasse ermöglicht das parallelitätssichere Anfügen von und Zugreifen auf Elemente. Vorhandene Zeiger und Iteratoren bleiben bei Anfügevorgängen gültig. Iteratorzugriff und Durchlaufvorgänge sind ebenfalls parallelitätssicher.

Unterschiede zwischen concurrent_vector und vector

Die concurrent_vector-Klasse stimmt mit der vector-Klasse weitgehend überein. Anfügevorgänge, Elementzugriff und Iteratorzugriff sind für ein concurrent_vector-Objekt genauso komplex wie für ein vector-Objekt. Im Folgenden sind die Unterschiede zwischen concurrent_vector und vector aufgeführt:

  • Anfügevorgänge, Elementzugriff, Iteratorzugriff und Iteratordurchläufe sind bei einem concurrent_vector-Objekt parallelitätssicher.

  • Das Hinzufügen von Elementen ist nur am Ende eines concurrent_vector-Objekts möglich. Die concurrent_vector-Klasse stellt die insert-Methode nicht bereit.

  • Das Anfügen an ein concurrent_vector-Objekt erfolgt ohne Verschiebesemantik.

  • Die concurrent_vector-Klasse stellt weder die erase-Methode noch die pop_back-Methode bereit. Verwenden Sie wie bei vector die clear-Methode, wenn alle Elemente aus einem concurrent_vector-Objekt entfernt werden sollen.

  • Die Elemente der concurrent_vector-Klasse werden nicht zusammenhängend im Arbeitsspeicher gespeichert. Daher bietet die concurrent_vector-Klasse nicht alle Möglichkeiten eines Arrays. Für eine Variable mit dem Namen v vom Typ concurrent_vector ergibt der Ausdruck &v[0]+2 beispielsweise nicht definiertes Verhalten.

  • Die concurrent_vector-Klasse definiert die grow_by-Methode und die grow_to_at_least-Methode. Diese Methoden entsprechen der resize-Methode, sind aber parallelitätssicher.

  • Die Elemente eines concurrent_vector-Objekts werden nicht verschoben, wenn Sie Elemente daran anfügen oder die Größe ändern. So bleiben vorhandene Zeiger und Iteratoren bei gleichzeitigen Vorgängen gültig.

  • Zur Laufzeit wird keine spezialisierte Version von concurrent_vector für den bool-Typ definiert.

Parallelitätssichere Vorgänge

Alle Methoden zum Anfügen an ein concurrent_vector-Objekt, Vergrößern des Objekts oder Zugreifen auf ein Element in einem concurrent_vector-Objekt sind parallelitätssicher. Eine Ausnahme von dieser Regel ist die resize-Methode.

Die folgende Tabelle enthält alle gängigen parallelitätssicheren concurrent_vector-Methoden und -Operatoren.

at

end

operator[]

begin

front

push_back

back

grow_by

rbegin

capacity

grow_to_at_least

rend

empty

max_size

size

Vorgänge, die zur Laufzeit für die Kompatibilität mit der STL bereitgestellt werden, wie z. B. reserve, sind nicht parallelitätssicher. Die folgende Tabelle enthält alle gängigen Methoden und Operatoren, die nicht parallelitätssicher sind.

assign

reserve

clear

resize

operator=

shrink_to_fit

Vorgänge, mit denen der Wert vorhandener Elemente geändert wird, sind nicht parallelitätssicher. Verwenden Sie ein Synchronisierungsobjekt, wie z. B. ein reader_writer_lock-Objekt, um gleichzeitige Lese- und Schreibvorgänge für das gleiche Datenelement zu synchronisieren. Weitere Informationen zu Synchronisierungsobjekten finden Sie unter Synchronisierungsdatenstrukturen.

Wenn Sie vorhandenen Code konvertieren, sodass dieser nicht mehr vector, sondern concurrent_vector verwendet, kann sich das Verhalten Ihrer Anwendung aufgrund von gleichzeitigen Vorgängen ändern. Betrachten Sie beispielsweise das folgende Programm, das gleichzeitig zwei Aufgaben für ein concurrent_vector-Objekt ausführt. Die erste Aufgabe fügt an ein concurrent_vector-Objekt zusätzliche Elemente an. Die zweite Aufgabe berechnet die Summe aller Elemente diesem 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 = v.begin(); i != v.end(); ++i) 
         {
            sums.local() += *i;
         }     
         wcout << L"sum = " << sums.combine(plus<int>()) << endl;
      }
   );
}

Die end-Methode ist zwar parallelitätssicher, aber ein gleichzeitiger Aufruf der push_back-Methode bewirkt, dass sich der von end zurückgegebene Wert ändert. Die Anzahl von Elementen, die der Iterator durchläuft, ist unbestimmt. Deshalb kann dieses Programm bei jeder Ausführung ein anderes Ergebnis liefern.

Ausnahmesicherheit

Im Falle einer Ausnahme bei einem Zuwachs- oder Zuweisungsvorgang wird der Zustand des concurrent_vector-Objekts ungültig. Das Verhalten eines concurrent_vector-Objekts mit ungültigem Zustand ist nicht definiert, falls nicht anders angegeben. Der vom Objekt zugewiesene Speicher wird jedoch vom Destruktor immer freigegeben, auch wenn der Zustand des Objekts ungültig ist.

Der Datentyp _Ty der Vektorelemente muss die folgenden Anforderungen erfüllen. Andernfalls ist das Verhalten der concurrent_vector-Klasse nicht definiert.

  • Der Destruktor darf nicht auslösen.

  • Wenn der Standard- oder Kopierkonstruktor auslöst, darf der Destruktor nicht mit dem virtual-Schlüsselwort deklariert werden und muss ordnungsgemäß mit nullinitialisiertem Arbeitsspeicher funktionieren.

[Nach oben]

concurrent_queue-Klasse

Die Concurrency::concurrent_queue-Klasse bietet wie die std::queue-Klasse Zugriff auf ihre front- und back-Elemente. Die concurrent_queue-Klasse ermöglicht parallelitätssichere enqueue- und dequeue-Vorgänge. Die concurrent_queue-Klasse bietet außerdem nicht parallelitätssichere Iteratorunterstützung.

Unterschiede zwischen concurrent_queue und queue

Die concurrent_queue-Klasse stimmt mit der queue-Klasse weitgehend überein. Im Folgenden sind die Unterschiede zwischen concurrent_queue und queue aufgeführt:

  • Enqueue- und dequeue-Vorgänge für ein concurrent_queue-Objekt sind parallelitätssicher.

  • Die concurrent_queue-Klasse bietet nicht parallelitätssichere Iteratorunterstützung.

  • Die concurrent_queue-Klasse stellt weder die front-Methode noch die pop-Methode bereit. Durch das Definieren der try_pop-Methode ersetzt die concurrent_queue-Klasse diese Methoden.

  • Die concurrent_queue-Klasse stellt die back-Methode nicht bereit. Daher können Sie nicht auf das Ende der Warteschlange verweisen.

  • Die concurrent_queue-Klasse stellt die unsafe_size-Methode statt der size-Methode bereit. Die unsafe_size-Methode ist nicht parallelitätssicher.

Parallelitätssichere Vorgänge

Alle Methoden für enqueue- und dequeue-Vorgänge eines concurrent_queue-Objekts sind parallelitätssicher.

Die folgende Tabelle enthält alle gängigen parallelitätssicheren concurrent_queue-Methoden und -Operatoren.

empty

push

get_allocator

try_pop

Obwohl die empty-Methode parallelitätssicher ist, kann ein gleichzeitiger Vorgang zur Folge haben, dass die Warteschlange vergrößert oder verkleinert wird, bevor die empty-Methode einen Wert zurückgibt.

Die folgende Tabelle enthält alle gängigen Methoden und Operatoren, die nicht parallelitätssicher sind.

clear

unsafe_end

unsafe_begin

unsafe_size

Iteratorunterstützung

Die concurrent_queue-Klasse stellt Iteratoren bereit, die nicht parallelitätssicher sind. Diese Iteratoren sollten nur zum Debuggen verwendet werden.

Ein concurrent_queue-Iterator durchläuft Elemente nur in Vorwärtsrichtung. Die folgende Tabelle enthält die von den einzelnen Iteratoren unterstützten Operatoren.

Operator

Beschreibung

operator++

Wechselt zum nächsten Element in der Warteschlange. Dieser Operator wird überladen, um sowohl Präinkrement- als auch Postinkrementsemantik bereitzustellen.

operator*

Ruft einen Verweis auf das aktuelle Element ab.

operator->

Ruft einen Zeiger auf das aktuelle Element ab.

[Nach oben]

combinable-Klasse

Die Concurrency::combinable-Klasse stellt wiederverwendbaren, lokalen Threadspeicher bereit, der Ihnen die Durchführung differenzierter Berechnungen und die Zusammenführung dieser Berechnungen in einem Endergebnis ermöglicht. Sie können sich ein combinable-Objekt wie eine reduction-Variable vorstellen.

Die combinable-Klasse ist nützlich, wenn Sie mit einer Ressource arbeiten, die von mehreren Threads bzw. Aufgaben gemeinsam genutzt wird. Mit der combinable-Klasse können Sie durch Bereitstellen von sperrenfreiem Zugriff auf freigegebene Ressourcen den Freigabezustand ausschließen. Daher bietet diese Klasse eine Alternative zur Verwendung eines Synchronisierungsmechanismus, z. B. eines Mutex, für die Synchronisierung des Zugriffs auf freigegebene Daten von mehreren Threads.

Methoden und Funktionen

Die folgende Tabelle enthält einige wichtige Methoden der combinable-Klasse. Informationen zu allen Methoden der combinable-Klasse finden Sie unter combinable-Klasse.

Methode

Beschreibung

local

Ruft einen Verweis auf die lokale Variable ab, die dem aktuellen Threadkontext zugeordnet ist.

clear

Entfernt alle lokalen Threadvariablen aus dem combinable-Objekt.

combine

combine_each

Verwendet die bereitgestellte combine-Funktion zur Generierung eines endgültigen Werts aus allen lokalen Threadberechnungen.

Die combinable-Klasse ist eine Vorlagenklasse, die auf der Grundlage des abschließenden zusammengeführten Ergebnisses parametrisiert wird. Wenn Sie den Standardkonstruktor aufrufen, muss der _Ty-Vorlagenparametertyp einen Standardkonstruktor und einen Kopierkonstruktor aufweisen. Wenn der _Ty-Vorlagenparametertyp keinen Standardkonstruktor aufweist, rufen Sie die überladene Version des Konstruktors auf, die eine Initialisierungsfunktion als Parameter akzeptiert.

Sie können weitere Daten in einem combinable-Objekt speichern, nachdem Sie die combine-Methode bzw. die combine_each-Methode aufgerufen haben. Außerdem können Sie die combine-Methode sowie die combine_each-Methode mehrmals aufrufen. Wenn sich in einem combinable-Objekt kein lokaler Wert ändert, führt jeder Aufruf der combine-Methode und der combine_each-Methode zum gleichen Ergebnis.

Beispiele

Beispiele zur Verwendung der combinable-Klasse finden Sie in den folgenden Themen:

[Nach oben]

Verwandte Themen

Referenzen

concurrent_vector-Klasse

concurrent_queue-Klasse

combinable-Klasse