Asynchrone Nachrichtenblöcke
Die Agents Library stellt mehrere Nachrichtenblocktypen bereit, mit deren Hilfe Nachrichten threadsicher zwischen Anwendungskomponenten weitergegeben werden können. Diese Nachrichtenblocktypen werden häufig mit den verschiedenen Nachrichtenübergaberoutinen verwendet, z . B. "concurrency::send", "concurrency::asend", "concurrency::receive" und "concurrency::try_receive". Weitere Informationen zu den Nachrichtenübergaberoutinen, die von der Agents-Bibliothek definiert werden, finden Sie unter Message Passing Functions.
Abschnitte
Dieses Thema enthält folgende Abschnitte:
Quellen und Ziele
Quelle und Ziel sind zwei wichtige Beteiligte bei der Nachrichtenübergabe. Eine Quelle bezieht sich auf einen Endpunkt der Kommunikation, der Nachrichten sendet. Ein Ziel bezieht sich auf einen Endpunkt der Kommunikation, der Nachrichten empfängt. Sie können sich eine Quelle als Endpunkt vorstellen, von dem gelesen wird, und ein Ziel als Endpunkt, in den geschrieben wird. Anwendungen verbinden Quellen und Ziele zusammen, um Messagingnetzwerke zu bilden.
Die Agents-Bibliothek verwendet zwei abstrakte Klassen, um Quellen und Ziele darzustellen: Parallelität::ISource und Parallelität::ITarget. Nachrichtenblocktypen, die als Quelle dienen, werden von der ISource
-Klasse abgeleitet, während Nachrichtenblocktypen, die als Ziel dienen, von der ITarget
-Klasse abgeleitet werden. Nachrichtenblocktypen, die als Quelle und Ziel dienen, werden von der ISource
-Klasse und der ITarget
-Klasse abgeleitet.
Nachrichtenverteilung
Die Nachrichtenverteilung ist der Akt des Sendens einer Nachricht von einer Komponente an eine andere. Wenn eine Nachricht für einen Nachrichtenblock bereitgestellt wird, kann er diese Nachricht akzeptieren, ablehnen oder verschieben. Jeder Nachrichtenblocktyp speichert und überträgt Nachrichten auf unterschiedliche Weise. Beispielsweise speichert die unbounded_buffer
-Klasse eine unendliche Anzahl von Nachrichten, die overwrite_buffer
-Klasse speichert jeweils nur eine Nachricht, und die Transformatorklasse speichert eine geänderte Version jeder Nachricht. Diese Nachrichtenblocktypen werden im Folgenden ausführlicher beschrieben.
Wenn ein Nachrichtenblock eine Meldung akzeptiert, können optionale Arbeiten durchgeführt werden, und wenn es sich bei dem Nachrichtenblock um eine Quelle handelt, kann die resultierende Nachricht an einen anderen Member im Netzwerk weitergegeben werden. Mithilfe einer Filterfunktion können Nachrichtenblöcke einzelne Nachrichten ablehnen, die nicht empfangen werden sollen. Filter werden weiter unten in diesem Thema im Abschnitt Nachrichtenfilterung ausführlicher beschrieben. Ein Nachrichtenblock, der eine Meldung verschiebt, kann sie reservieren und später verarbeiten. Die Nachrichtenreservierung wird weiter unten in diesem Thema im Abschnitt Nachrichtenreservierung beschrieben.
Mit der Agents Library können Nachrichten von Nachrichtenblöcken synchron oder asynchron übergeben werden. Wenn Sie eine Nachricht synchron an einen Nachrichtenblock übergeben, z. B. mit der send
-Funktion, wird der aktuelle Kontext von der Laufzeit blockiert, bis die Nachricht vom Zielblock akzeptiert oder abgelehnt wird. Wenn Sie eine Nachricht asynchron an einen Nachrichtenblock übergeben, z. B. mit der asend
-Funktion, wird die Nachricht von der Laufzeit für das Ziel bereitgestellt, und wenn die Nachricht vom Ziel akzeptiert wird, wird von der Laufzeit eine asynchrone Aufgabe geplant, um die Nachricht an den Empfänger weiterzugeben. Die Laufzeit verwendet einfache Aufgaben, um Nachrichten auf kooperative Weise weiterzugeben. Weitere Informationen zu einfachen Vorgängen finden Sie unter "Task Scheduler".
Anwendungen verbinden Quellen und Ziele, und bilden so Messagingnetzwerke. Normalerweise verknüpfen Sie das Netzwerk, und rufen send
oder asend
auf, um Daten an das Netzwerk zu übergeben. Um einen Quellnachrichtenblock mit einem Ziel zu verbinden, rufen Sie die Parallelität::ISource::link_target-Methode auf. Um einen Quellblock von einem Ziel zu trennen, rufen Sie die Parallelität::ISource::unlink_target-Methode auf. Um einen Quellblock von allen Zielen zu trennen, rufen Sie die Parallelität::ISource::unlink_targets-Methode auf. Wenn sich einer der vordefinierten Nachrichtenblocktypen nicht mehr im Bereich befindet oder zerstört wird, werden die Verknüpfungen mit den Zielblöcken automatisch aufgehoben. Bei einigen Nachrichtenblocktypen ist die maximale Anzahl der Ziele eingeschränkt, in die geschrieben werden kann. Im folgenden Abschnitt werden die Einschränkungen beschrieben, die für die vordefinierten Nachrichtenblocktypen gelten.
Übersicht über Nachrichtenblocktypen
In der folgenden Tabelle wird die Rolle der wichtigen Nachrichtenblocktypen kurz beschrieben.
unbounded_buffer
Speichert eine Nachrichtenwarteschlange.
overwrite_buffer
Speichert eine Nachricht, die mehrmals geschrieben und gelesen werden kann.
single_assignment
Speichert eine Nachricht, die ein Mal geschrieben und gelesen werden kann.
call
Führt beim Empfang einer Nachricht Arbeiten aus.
Transformator
Führt Arbeiten aus, wenn Daten empfangen werden, und sendet das Ergebnis dieser Arbeiten an einen anderen Zielblock. Die transformer
-Klasse kann für unterschiedliche Eingabe- und Ausgabetypen verwendet werden.
choice
Wählt aus einer Gruppe Quellen die erste verfügbare Nachricht aus.
Join und Multitype-Verknüpfung
Warten, bis alle Nachrichten, die von einer Gruppe Quellen empfangen werden sollen, empfangen wurden, und setzen die Nachrichten zu einer Nachricht für einen anderen Nachrichtenblock zusammen.
timer
Sendet eine Nachricht in regelmäßigen Intervallen an einen Zielblock.
Diese Nachrichtenblocktypen haben unterschiedliche Eigenschaften, sodass sie für unterschiedliche Situationen nützlich sind. Zu diesen Eigenschaften zählen folgende:
Verteilungstyp: Gibt an, ob der Nachrichtenblock als Datenquelle, als Empfänger von Daten oder beides fungiert.
Nachrichtenreihenfolge: Gibt an, ob der Nachrichtenblock die ursprüngliche Reihenfolge beibehält, in der Nachrichten gesendet oder empfangen werden. Vordefinierte Nachrichtenblocktypen behalten die ursprüngliche Reihenfolge bei, in der Nachrichten gesendet oder empfangen werden.
Quellanzahl: Die maximale Anzahl von Quellen, aus denen der Nachrichtenblock lesen kann.
Zielanzahl: Die maximale Anzahl von Zielen, in die der Nachrichtenblock schreiben kann.
In der folgenden Tabelle wird der Bezug dieser Eigenschaften auf die verschiedenen Nachrichtenblocktypen beschrieben.
Nachrichtenblocktyp | Weitergabetyp (Quelle, Ziel oder beides) | Nachrichtenreihenfolge (sortiert oder nicht sortiert) | Quellenanzahl | Zielanzahl |
---|---|---|---|---|
unbounded_buffer |
Beides | Bestellt | Unbegrenzt | Unbegrenzt |
overwrite_buffer |
Beides | Bestellt | Unbegrenzt | Unbegrenzt |
single_assignment |
Beides | Bestellt | Unbegrenzt | Unbegrenzt |
call |
Ziel | Bestellt | Unbegrenzt | Nicht zutreffend |
transformer |
Beides | Bestellt | Unbegrenzt | 1 |
choice |
Beide | Bestellt | 10 | 1 |
join |
Beide | Bestellt | Unbegrenzt | 1 |
multitype_join |
Beide | Bestellt | 10 | 1 |
timer |
Quelle | Nicht zutreffend | Nicht zutreffend | 1 |
In den folgenden Abschnitten werden die Nachrichtenblocktypen ausführlicher beschrieben.
unbounded_buffer-Klasse
Die Parallelität::unbounded_buffer Klasse stellt eine allgemeine asynchrone Messagingstruktur dar. Diese Klasse speichert eine FIFO-Nachrichtenwarteschlange (First In, First Out), in die mehrere Quellen Nachrichten schreiben oder aus der mehrere Ziele Nachrichten auslesen können. Wenn ein Ziel eine Nachricht von einem unbounded_buffer
-Objekt empfängt, wird diese Nachricht aus der Nachrichtenwarteschlange entfernt. Daher können die einzelnen Nachrichten nur von einem Ziel empfangen werden, obwohl ein unbounded_buffer
-Objekt mehrere Ziele haben kann. Die unbounded_buffer
-Klasse ist hilfreich, wenn Sie mehrere Nachrichten an eine andere Komponente übergeben möchten und diese Komponente alle Nachrichten empfangen muss.
Beispiel
Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der unbounded_buffer
-Klasse veranschaulicht. In diesem Beispiel werden drei Werte an ein unbounded_buffer
-Objekt gesendet und wieder von diesem Objekt zurückgegeben.
// unbounded_buffer-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// Create an unbounded_buffer object that works with
// int data.
unbounded_buffer<int> items;
// Send a few items to the unbounded_buffer object.
send(items, 33);
send(items, 44);
send(items, 55);
// Read the items from the unbounded_buffer object and print
// them to the console.
wcout << receive(items) << endl;
wcout << receive(items) << endl;
wcout << receive(items) << endl;
}
Dieses Beispiel erzeugt die folgende Ausgabe:
334455
Ein vollständiges Beispiel zur Verwendung der unbounded_buffer
Klasse finden Sie unter How to: Implement Various Producer-Consumer Patterns.
overwrite_buffer-Klasse
Die Parallelität::overwrite_buffer Klasse ähnelt der unbounded_buffer
Klasse, mit der Ausnahme, dass ein overwrite_buffer
Objekt nur eine Nachricht speichert. Wenn ein Ziel eine Nachricht von einem overwrite_buffer
-Objekt empfängt, wird diese Nachricht darüber hinaus auch nicht aus dem Puffer entfernt. Daher empfangen mehrere Ziele eine Kopie der Nachricht.
Die overwrite_buffer
-Klasse ist hilfreich, wenn Sie mehrere Nachrichten an eine andere Komponente übergeben möchten, diese Komponente jedoch nur den letzten Wert benötigt. Diese Klasse ist darüber hinaus auch hilfreich, wenn Sie eine Nachricht an mehreren Komponenten übertragen möchten.
Beispiel
Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der overwrite_buffer
-Klasse veranschaulicht. In diesem Beispiel werden drei Werte an ein overwrite _buffer
-Objekt gesendet, und der aktuelle Wert wird dreimal aus dem gleichen Objekt gelesen. Dieses Beispiel ähnelt dem Beispiel für die unbounded_buffer
-Klasse. Allerdings speichert die overwrite_buffer
-Klasse nur eine Nachricht. Darüber hinaus wird die Nachricht von der Laufzeit nicht aus einem overwrite_buffer
-Objekt entfernt, nachdem sie gelesen wurde.
// overwrite_buffer-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// Create an overwrite_buffer object that works with
// int data.
overwrite_buffer<int> item;
// Send a few items to the overwrite_buffer object.
send(item, 33);
send(item, 44);
send(item, 55);
// Read the current item from the overwrite_buffer object and print
// it to the console three times.
wcout << receive(item) << endl;
wcout << receive(item) << endl;
wcout << receive(item) << endl;
}
Dieses Beispiel erzeugt die folgende Ausgabe:
555555
Ein vollständiges Beispiel zur Verwendung der overwrite_buffer
Klasse finden Sie unter How to: Implement Various Producer-Consumer Patterns.
single_assignment-Klasse
Die Parallelität::single_assignment-Klasse ähnelt der overwrite_buffer
Klasse, mit der Ausnahme, dass ein single_assignment
Objekt nur einmal geschrieben werden kann. Wenn ein Ziel eine Nachricht von einem overwrite_buffer
-Objekt empfängt, wird diese Nachricht wie bei der single_assignment
-Klasse nicht aus diesem Objekt entfernt. Daher empfangen mehrere Ziele eine Kopie der Nachricht. Die single_assignment
-Klasse ist hilfreich, wenn Sie eine Nachricht an mehrere Komponenten übertragen möchten.
Beispiel
Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der single_assignment
-Klasse veranschaulicht. In diesem Beispiel werden drei Werte an ein single_assignment
-Objekt gesendet, und der aktuelle Wert wird dreimal aus dem gleichen Objekt gelesen. Dieses Beispiel ähnelt dem Beispiel für die overwrite_buffer
-Klasse. Die overwrite_buffer
-Klasse und die single_assignment
-Klasse speichern jeweils eine einzelne Nachricht, in die single_assignment
-Klasse kann jedoch nur einmal geschrieben werden.
// single_assignment-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// Create an single_assignment object that works with
// int data.
single_assignment<int> item;
// Send a few items to the single_assignment object.
send(item, 33);
send(item, 44);
send(item, 55);
// Read the current item from the single_assignment object and print
// it to the console three times.
wcout << receive(item) << endl;
wcout << receive(item) << endl;
wcout << receive(item) << endl;
}
Dieses Beispiel erzeugt die folgende Ausgabe:
333333
Ein vollständiges Beispiel zur Verwendung der single_assignment
Klasse finden Sie unter Walkthrough: Implementing Futures.
call-Klasse
Die Parallelitätsklasse::call fungiert als Nachrichtenempfänger, der eine Arbeitsfunktion ausführt, wenn sie Daten empfängt. Bei dieser Arbeitsfunktion kann es sich um einen Lambdaausdruck, ein Funktionsobjekt oder einen Funktionszeiger handeln. Ein call
-Objekt verhält sich anders als ein gewöhnlicher Funktionsaufruf, da es parallel zu anderen Komponenten agiert, die Nachrichten an das Objekt senden. Wenn ein call
-Objekt beim Empfang einer Nachricht Arbeiten ausführt, fügt es die empfangene Nachricht einer Warteschlange hinzu. Jedes call
-Objekt verarbeitet Nachrichten in der Warteschlange in der Reihenfolge, in der sie empfangen werden.
Beispiel
Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der call
-Klasse veranschaulicht. In diesem Beispiel wird ein call
-Objekt erstellt, das jeden empfangenen Wert an der Konsole ausgibt. Anschließend werden im Beispiel drei Werte an das call
-Objekt gesendet. Da das call
Objekt Nachrichten in einem separaten Thread verarbeitet, verwendet dieses Beispiel auch eine Zählervariable und ein Ereignisobjekt , um sicherzustellen, dass das call
Objekt alle Nachrichten verarbeitet, bevor die wmain
Funktion zurückgegeben wird.
// call-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// An event that is set when the call object receives all values.
event received_all;
// Counts the
long receive_count = 0L;
long max_receive_count = 3L;
// Create an call object that works with int data.
call<int> target([&received_all,&receive_count,max_receive_count](int n) {
// Print the value that the call object receives to the console.
wcout << n << endl;
// Set the event when all messages have been processed.
if (++receive_count == max_receive_count)
received_all.set();
});
// Send a few items to the call object.
send(target, 33);
send(target, 44);
send(target, 55);
// Wait for the call object to process all items.
received_all.wait();
}
Dieses Beispiel erzeugt die folgende Ausgabe:
334455
Ein vollständiges Beispiel zur Verwendung der call
Klasse finden Sie unter How to: Provide Work Functions to the call and transformer Classes.
transformer-Klasse
Die Parallelitätsklasse::transformer fungiert sowohl als Nachrichtenempfänger als auch als Nachrichtensender. Die transformer
-Klasse ist gleicht der call
-Klasse, da sie beim Empfang von Daten eine benutzerdefinierte Arbeitsfunktion ausführt. Die transformer
-Klasse sendet jedoch auch das Ergebnis der Arbeitsfunktion an Empfängerobjekte. Wie ein call
-Objekt agiert ein transformer
-Objekt parallel zu anderen Komponenten, die Nachrichten an das Objekt senden. Wenn ein transformer
-Objekt beim Empfang einer Nachricht Arbeiten ausführt, fügt es die empfangene Nachricht einer Warteschlange hinzu. Jedes transformer
-Objekt verarbeitet die eigenen Nachrichten in der Warteschlange in der Reihenfolge, in der sie empfangen werden.
Die transformer
-Klasse sendet die eigene Nachricht an ein Ziel. Wenn Sie den _PTarget
Parameter im Konstruktor auf NULL
festlegen, können Sie das Ziel später durch Aufrufen der Concurrency::link_target-Methode angeben.
Im Gegensatz zu allen anderen asynchronen Nachrichtenblocktypen, die von der Agents Library bereitgestellt werden, kann die transformer
-Klasse für unterschiedliche Eingabe- und Ausgabetypen verwendet werden. Aufgrund dieser Fähigkeit, Daten von einem Typ in einen anderen transformieren zu können, ist die transformer
-Klasse in vielen parallelen Netzwerken eine wichtige Komponente. Zudem können Sie differenziertere Parallelitätsfunktionen in die Arbeitsfunktion eines transformer
-Objekts integrieren.
Beispiel
Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der transformer
-Klasse veranschaulicht. In diesem Beispiel wird ein transformer
-Objekt erstellt, mit dem jeder int
-Eingabewert mit 0.33 multipliziert wird, um einen double
-Wert als Ausgabe zu generieren. Anschließend werden im Beispiel transformierten Werte vom selben transformer
-Objekt empfangen und an der Konsole ausgegeben.
// transformer-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// Create an transformer object that receives int data and
// sends double data.
transformer<int, double> third([](int n) {
// Return one-third of the input value.
return n * 0.33;
});
// Send a few items to the transformer object.
send(third, 33);
send(third, 44);
send(third, 55);
// Read the processed items from the transformer object and print
// them to the console.
wcout << receive(third) << endl;
wcout << receive(third) << endl;
wcout << receive(third) << endl;
}
Dieses Beispiel erzeugt die folgende Ausgabe:
10.8914.5218.15
Ein vollständiges Beispiel zur Verwendung der transformer
Klasse finden Sie unter How to: Use transformer in a Data Pipeline.
choice-Klasse
Die Parallelitätsklasse::choice wählt die erste verfügbare Nachricht aus einer Reihe von Quellen aus. Die choice
Klasse stellt einen Kontrollflussmechanismus anstelle eines Datenflussmechanismus dar (im Thema "Asynchrone Agents-Bibliothek" werden die Unterschiede zwischen Datenfluss und Kontrollfluss beschrieben).
Der Lesevorgang bei einem choice-Objekt gleicht dem Aufruf der API-Funktion WaitForMultipleObjects
von Windows, wenn der bWaitAll
-Parameter auf FALSE
festgelegt ist. Die choice
-Klasse bindet Daten jedoch nicht an ein externes Synchronisierungsobjekt, sondern an das Ereignis selbst.
In der Regel verwenden Sie die choice
Klasse zusammen mit der Parallelitätsfunktion::receive , um den Steuerungsfluss in Ihrer Anwendung zu steuern. Verwenden Sie die choice
-Klasse, wenn Sie unter Nachrichtenpuffern auswählen müssen, die andere Typen aufweisen. Verwenden Sie die single_assignment
-Klasse, wenn Sie unter Nachrichtenpuffern auswählen müssen, die denselben Typ aufweisen.
Es ist wichtig, in welcher Reihenfolge Quellen mit einem choice
-Objekt verknüpft werden, da die Reihenfolge bestimmen kann, welche Nachricht ausgewählt wird. Stellen Sie sich beispielsweise den Fall vor, dass mehrere Nachrichtenpuffer, die bereits eine Nachricht enthalten, mit einem choice
-Objekt verknüpft werden. Das choice
-Objekt wählt die Nachricht aus der ersten Quelle aus, mit der es verknüpft wird. Nach der Verknüpfung aller Quellen behält das choice
-Objekt die Reihenfolge, in der die einzelnen Quellen eine Nachricht empfangen, bei.
Beispiel
Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der choice
-Klasse veranschaulicht. In diesem Beispiel wird die Parallelität::make_choice-Funktion verwendet, um ein choice
Objekt zu erstellen, das zwischen drei Nachrichtenblöcken auswählt. Anschließend werden im Beispiel verschiedene Fibonacci-Zahlen berechnet, und jedes Ergebnis wird in einem anderen Nachrichtenblock gespeichert. Im Beispiel wird dann in der Konsole eine Meldung angezeigt, die auf dem Vorgang basiert, der zuerst beendet wurde.
// choice-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <ppl.h>
#include <iostream>
using namespace concurrency;
using namespace std;
// Computes the nth Fibonacci number.
// This function illustrates a lengthy operation and is therefore
// not optimized for performance.
int fibonacci(int n)
{
if (n < 2)
return n;
return fibonacci(n-1) + fibonacci(n-2);
}
int wmain()
{
// Although the following thee message blocks are written to one time only,
// this example illustrates the fact that the choice class works with
// different message block types.
// Holds the 35th Fibonacci number.
single_assignment<int> fib35;
// Holds the 37th Fibonacci number.
overwrite_buffer<int> fib37;
// Holds half of the 42nd Fibonacci number.
unbounded_buffer<double> half_of_fib42;
// Create a choice object that selects the first single_assignment
// object that receives a value.
auto select_one = make_choice(&fib35, &fib37, &half_of_fib42);
// Execute a few lengthy operations in parallel. Each operation sends its
// result to one of the single_assignment objects.
parallel_invoke(
[&fib35] { send(fib35, fibonacci(35)); },
[&fib37] { send(fib37, fibonacci(37)); },
[&half_of_fib42] { send(half_of_fib42, fibonacci(42) * 0.5); }
);
// Print a message that is based on the operation that finished first.
switch (receive(select_one))
{
case 0:
wcout << L"fib35 received its value first. Result = "
<< receive(fib35) << endl;
break;
case 1:
wcout << L"fib37 received its value first. Result = "
<< receive(fib37) << endl;
break;
case 2:
wcout << L"half_of_fib42 received its value first. Result = "
<< receive(half_of_fib42) << endl;
break;
default:
wcout << L"Unexpected." << endl;
break;
}
}
Dieses Beispiel erzeugt die folgende Beispielausgabe:
fib35 received its value first. Result = 9227465
Da der Vorgang, der die 35. Fibonacci-Zahl berechnet, nicht zuerst abgeschlossen werden kann, kann die Ausgabe dieses Beispiels variieren.
In diesem Beispiel wird der Algorithmus "concurrency::p arallel_invoke " verwendet, um die Fibonacci-Zahlen parallel zu berechnen. Weitere Informationen finden parallel_invoke
Sie unter parallele Algorithmen.
Ein vollständiges Beispiel zur Verwendung der choice
Klasse finden Sie unter How to: Select Among Completed Tasks.For a complete example that shows how to: Select Among Completed Tasks.
Teilnehmen und multitype_join Klassen
Mit der Parallelität::join und parallelität::multitype_join Klassen können Sie warten, bis jedes Element einer Reihe von Quellen eine Nachricht empfängt. Die join
-Klasse wird für Quellobjekte verwendet, die einen allgemeinen Nachrichtentyp aufweisen. Die multitype_join
-Klasse wird für Quellobjekte verwendet, die andere Nachrichtentypen aufweisen können.
Der Lesevorgang bei einem join
oder einem multitype_join
-Objekt ähnelt dem Aufrufen der API-Funktion WaitForMultipleObjects
von Windows, wenn der bWaitAll
-Parameter auf TRUE
festgelegt ist. Ähnlich wie ein choice
-Objekt verwenden das join
- und das multitype_join
-Objekt einen Ereignismechanismus, der Daten nicht an ein externes Synchronisierungsobjekt, sondern an das Ereignis selbst bindet.
Das Lesen aus einem join
Objekt erzeugt ein std::vector-Objekt . Das Lesen aus einem multitype_join
Objekt erzeugt ein std::tuple-Objekt . Elemente treten in diesen Objekten in der Reihenfolge auf, in der die entsprechenden Quellpuffer mit dem join
-Objekt oder mit dem multitype_join
-Objekt verknüpft werden. Da die Reihenfolge, in der Quellpuffer mit einem join
-Objekt oder einem multitype_join
-Objekt verknüpft werden, von der Reihenfolge der Elemente im resultierenden vector
-Objekt oder tuple
-Objekt abhängig ist, wird empfohlen, die Verknüpfung zwischen einem vorhandenen Quellpuffer und einem Join nicht aufzuheben. Andernfalls ist das daraus folgende Verhalten möglicherweise undefiniert.
Gierige und nicht gierige Joins
Die join
-Klasse und die multitype_join
-Klasse unterstützen das Konzept gieriger und nicht gieriger Joins. Eine gierige Verknüpfung akzeptiert eine Nachricht aus jeder seiner Quellen, sobald Nachrichten verfügbar sind, bis alle Nachrichten verfügbar sind. Ein nicht gieriger Beitritt empfängt Nachrichten in zwei Phasen. Zunächst wartet ein nicht gieriger Join, bis eine Nachricht aus eine der Quelle bereitgestellt wird. Nachdem alle Quellmeldungen verfügbar sind, versucht ein nicht gieriger Join dann, diese Nachrichten zu reservieren. Wenn alle Nachrichten reserviert werden können, werden die einzelnen Nachrichten verarbeitet und an das Ziel weitergegeben. Wenn dies nicht der Fall ist, werden die Nachrichtenreservierungen freigegeben oder abgebrochen und es wird gewartet, bis die einzelnen Quellen eine Nachricht empfangen.
Gierige Joins erzielen im Vergleich zu nicht gierigen Joins eine bessere Leistung, da sie Nachrichten sofort empfangen. In seltenen Fällen können gierige Joins jedoch zu Deadlocks führen. Verwenden Sie einen nicht gierigen Join, wenn Sie mehrere Joins haben, die ein oder mehrere gemeinsame Quellobjekte enthalten.
Beispiel
Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der join
-Klasse veranschaulicht. In diesem Beispiel wird die Parallelität::make_join-Funktion verwendet, um ein join
Objekt zu erstellen, das von drei single_assignment
Objekten empfangen wird. In diesem Beispiel werden verschiedene Fibonacci-Zahlen berechnet, das jeweilige Ergebnis wird in einem eigenen single_assignment
-Objekt gespeichert, und die einzelnen Ergebnisse im join
-Objekt werden an der Konsole ausgegeben. Dieses Beispiel ähnelt dem Beispiel für die choice
-Klasse; im Unterschied dazu wartet die join
-Klasse jedoch, bis alle Nachrichtenblöcke eine Nachricht empfangen haben.
// join-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <ppl.h>
#include <iostream>
using namespace concurrency;
using namespace std;
// Computes the nth Fibonacci number.
// This function illustrates a lengthy operation and is therefore
// not optimized for performance.
int fibonacci(int n)
{
if (n < 2)
return n;
return fibonacci(n-1) + fibonacci(n-2);
}
int wmain()
{
// Holds the 35th Fibonacci number.
single_assignment<int> fib35;
// Holds the 37th Fibonacci number.
single_assignment<int> fib37;
// Holds half of the 42nd Fibonacci number.
single_assignment<double> half_of_fib42;
// Create a join object that selects the values from each of the
// single_assignment objects.
auto join_all = make_join(&fib35, &fib37, &half_of_fib42);
// Execute a few lengthy operations in parallel. Each operation sends its
// result to one of the single_assignment objects.
parallel_invoke(
[&fib35] { send(fib35, fibonacci(35)); },
[&fib37] { send(fib37, fibonacci(37)); },
[&half_of_fib42] { send(half_of_fib42, fibonacci(42) * 0.5); }
);
auto result = receive(join_all);
wcout << L"fib35 = " << get<0>(result) << endl;
wcout << L"fib37 = " << get<1>(result) << endl;
wcout << L"half_of_fib42 = " << get<2>(result) << endl;
}
Dieses Beispiel erzeugt die folgende Ausgabe:
fib35 = 9227465fib37 = 24157817half_of_fib42 = 1.33957e+008
In diesem Beispiel wird der Algorithmus "concurrency::p arallel_invoke " verwendet, um die Fibonacci-Zahlen parallel zu berechnen. Weitere Informationen finden parallel_invoke
Sie unter parallele Algorithmen.
Vollständige Beispiele zur Verwendung der join
Klasse finden Sie unter How to: Select Among Completed Tasks and Walkthrough: Using join to Prevent Deadlock.
timer-Klasse
Die Parallelitätsklasse::timer fungiert als Nachrichtenquelle. Ein timer
-Objekt sendet nach Ablauf einer angegebenen Zeitdauer eine Nachricht an ein Ziel. Die timer
-Klasse ist hilfreich, wenn der Versand einer Nachricht verzögert werden muss oder wenn eine Nachricht in regelmäßigen Intervallen gesendet werden muss.
Die timer
-Klasse sendet die eigene Nachricht an nur ein Ziel. Wenn Sie den _PTarget
Parameter im Konstruktor auf NULL
festlegen, können Sie das Ziel später angeben, indem Sie die Parallelität::ISource::link_target-Methode aufrufen.
Ein timer
-Objekt kann ein sich wiederholendes oder ein sich nicht wiederholendes Objekt sein. Um einen wiederholten Timer zu erstellen, übergeben true
Sie den _Repeating
Parameter, wenn Sie den Konstruktor aufrufen. Übergeben Sie false
andernfalls den _Repeating
Parameter, um einen nicht wiederholten Timer zu erstellen. Wenn das Timer-Objekt ein sich wiederholendes Objekt ist, wird dieselbe Nachricht nach jedem Intervall an das entsprechende Ziel gesendet.
Die Agents Library erstellt timer
-Objekte im nicht gestarteten Zustand. Rufen Sie zum Starten eines Timerobjekts die Parallelität::timer::start-Methode auf. Um ein timer
Objekt zu beenden, zerstören Sie das Objekt, oder rufen Sie die Parallelität::timer::stop-Methode auf. Rufen Sie zum Anhalten eines wiederholten Timers die Parallelität::timer::p ause-Methode auf.
Beispiel
Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der timer
-Klasse veranschaulicht. Im Beispiel wird mit dem timer
-Objekt und dem call
-Objekt der Status eines längeren Vorgangs angegeben.
// timer-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>
using namespace concurrency;
using namespace std;
// Computes the nth Fibonacci number.
// This function illustrates a lengthy operation and is therefore
// not optimized for performance.
int fibonacci(int n)
{
if (n < 2)
return n;
return fibonacci(n-1) + fibonacci(n-2);
}
int wmain()
{
// Create a call object that prints characters that it receives
// to the console.
call<wchar_t> print_character([](wchar_t c) {
wcout << c;
});
// Create a timer object that sends the period (.) character to
// the call object every 100 milliseconds.
timer<wchar_t> progress_timer(100u, L'.', &print_character, true);
// Start the timer.
wcout << L"Computing fib(42)";
progress_timer.start();
// Compute the 42nd Fibonacci number.
int fib42 = fibonacci(42);
// Stop the timer and print the result.
progress_timer.stop();
wcout << endl << L"result is " << fib42 << endl;
}
Dieses Beispiel erzeugt die folgende Beispielausgabe:
Computing fib(42)..................................................result is 267914296
Ein vollständiges Beispiel zur Verwendung der timer
Klasse finden Sie unter How to: Send a Message at a Regular Interval.
Nachrichtenfilterung
Wenn Sie ein Nachrichtenblockobjekt erstellen, können Sie eine Filterfunktion bereitstellen, die bestimmt, ob der Nachrichtenblock eine Nachricht akzeptiert oder ablehnt. Eine Filterfunktion ist eine hilfreiche Möglichkeit, um sicherzustellen, dass nur bestimmte Werte von einem Nachrichtenblock empfangen werden.
Im folgenden Beispiel wird veranschaulicht, wie ein unbounded_buffer
-Objekt erstellt wird, das eine Filterfunktion verwendet, um nur gerade Zahlen zu akzeptieren. Ungerade Zahlen werden vom unbounded_buffer
-Objekt zurückgewiesen, sodass ungerade Zahlen nicht an die Zielblöcke weitergegeben werden.
// filter-function.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// Create an unbounded_buffer object that uses a filter
// function to accept only even numbers.
unbounded_buffer<int> accept_evens(
[](int n) {
return (n%2) == 0;
});
// Send a few values to the unbounded_buffer object.
unsigned int accept_count = 0;
for (int i = 0; i < 10; ++i)
{
// The asend function returns true only if the target
// accepts the message. This enables us to determine
// how many elements are stored in the unbounded_buffer
// object.
if (asend(accept_evens, i))
{
++accept_count;
}
}
// Print to the console each value that is stored in the
// unbounded_buffer object. The unbounded_buffer object should
// contain only even numbers.
while (accept_count > 0)
{
wcout << receive(accept_evens) << L' ';
--accept_count;
}
}
Dieses Beispiel erzeugt die folgende Ausgabe:
0 2 4 6 8
Eine Filterfunktion kann eine Lambda-Funktion, ein Funktionsobjekt oder ein Funktionszeiger sein. Jede Filterfunktion nimmt eines der folgenden Formate an.
bool (T)
bool (T const &)
Um das unnötige Kopieren von Daten zu vermeiden, verwenden Sie das zweite Format bei einem aggregierten Typ, der anhand des Werts weitergegeben wird.
Die Nachrichtenfilterung unterstützt das Datenflussprogrammiermodell , bei dem Komponenten Berechnungen durchführen, wenn sie Daten empfangen. Beispiele, die Filterfunktionen verwenden, um den Datenfluss in einem Nachrichtenübergabenetzwerk zu steuern, finden Sie unter How to: Use a Message Block Filter, Walkthrough: Creating a Dataflow Agent, and Walkthrough: Creating an Image Processing Network.
Nachrichtenreservierung
Die Nachrichtenreservierung ermöglicht es einem Nachrichtenblock, eine Nachricht zur späteren Verwendung zu reservieren. In aller Regel wird die Nachrichtenreservierung nicht direkt verwendet. Kenntnisse der Nachrichtenreservierung helfen Ihnen jedoch möglicherweise, das Verhalten vordefinierter Nachrichtenblocktypen besser zu verstehen.
Beispiel: gierige und nicht gierige Joins. Beide Jointypen verwenden die Nachrichtenreservierung, um Meldungen für die spätere Verwendung zu reservieren. Wie bereits beschrieben, werden Nachrichten bei nicht gierigen Joins in zwei Phasen empfangen. In der ersten Phase wartet ein nicht gieriges join
-Objekt, bis von den einzelnen Quellen eine Nachricht empfangen wurde. Ein nicht gieriger Join versucht anschließend, jede dieser Nachrichten zu reservieren. Wenn alle Nachrichten reserviert werden können, werden die einzelnen Nachrichten verarbeitet und an das Ziel weitergegeben. Wenn dies nicht der Fall ist, werden die Nachrichtenreservierungen freigegeben oder abgebrochen und es wird gewartet, bis die einzelnen Quellen eine Nachricht empfangen.
Bei einem gierigen Join, der auch Eingabenachrichten aus einer Reihe von Quellen liest, werden mithilfe der Nachrichtenreservierung weitere Nachrichten gelesen, während darauf gewartet wird, dass eine Nachricht von jeder Quelle empfangen wird. Angenommen, ein gieriger Join empfängt Nachrichten vom Nachrichtenblock A
und vom Nachrichtenblock B
. Wenn der gierige Join zwei Nachrichten von B empfängt, jedoch noch keine Nachrichten von A
erhalten hat, wird die eindeutige Nachrichten-ID für die zweite Nachrichten von B
vom gierigen Join gespeichert. Nachdem der gierige Join eine Meldung von A
empfangen hat und diese Nachrichten weitergibt, wird anhand dieser Nachrichten-ID ermittelt, ob die zweite Nachricht von B
weiterhin verfügbar ist.
Sie können die Nachrichtenreservierung verwenden, wenn Sie eigene Nachrichtenblocktypen implementieren. Ein Beispiel zum Erstellen eines benutzerdefinierten Nachrichtenblocktyps finden Sie unter Walkthrough: Creating a Custom Message Block.