Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Die Agents Library stellt mehrere Nachrichtenblocktypen bereit, mit deren Hilfe Nachrichten threadsicher zwischen Anwendungskomponenten weitergegeben werden können. Diese Nachrichtblock-Typen werden häufig mit den verschiedenen Routinen zur Nachrichtenübergabe verwendet, wie z. B. concurrency::send, concurrency::asend, concurrency::receiveund concurrency::try_receive. Weitere Informationen zu den von der Agent-Bibliothek definierten Routinen zur Nachrichtenübermittlung finden Sie unter Funktionen zur Nachrichtenübermittlung.
Sections
Dieses Thema enthält folgende Abschnitte:
Quellen und Ziele
Quelle und Ziel sind zwei wichtige Beteiligte bei der Nachrichtenübergabe. Ein Quelle bezeichnet einen Endpunkt der Kommunikation, der Nachrichten sendet. Ein Ziel bezeichnet 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, und bilden so Messagingnetzwerke.
The Agents Library uses two abstract classes to represent sources und targets: concurrency::ISource und concurrency::ITarget. Nachrichtenblocktypen, die als Quellen ableiten ISource; Nachrichtenblocktypen, die als Ziele abgeleitet sind ITarget. Nachrichtenblocktypen, die als Quelle und Ziel dienen, werden von der ISource-Klasse und der ITarget-Klasse abgeleitet.
Propagierung von Nachrichten
Nachrichtenpropagierung ist der Vorgang des Sendens einer Nachricht von einer Komponente zu einer anderen. 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 später in diesem Thema näher beschrieben im Abschnitt Nachrichtenfilterung. Ein Nachrichtenblock, der eine Meldung verschiebt, kann sie reservieren und später verarbeiten. Die Nachricht Reservierung wird später in diesem Thema näher beschrieben im Abschnitt Nachricht Reservierung.
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 leichten Aufgaben finden Sie unter 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 Methode concurrency::ISource::link_target auf. Um einen Quellblock von einem Ziel zu trennen, rufen Sie die Methode concurrency::ISource::unlink_target angeben. Um einen Quellblock von allen seinen Zielen zu trennen, rufen Sie die Methode concurrency::ISource::unlink_targets aufruft. 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 die Arten von Meldungsblöcken
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.
Einzelzuweisung
Speichert eine Nachricht, die ein Mal geschrieben und gelesen werden kann.
Aufruf
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.
Auswahl
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:
Propagationstyp: Gibt an, ob der Nachrichtenblock als Datenquelle, Datenempfänger oder beides fungiert.
Nachrichtenreihenfolge: Gibt an, ob der Nachrichtenblock die ursprüngliche Reihenfolge beibehält, in der Nachrichten gesendet oder empfangen wurden. 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 |
Both | Ordered | Unbounded | Unbounded |
overwrite_buffer |
Both | Ordered | Unbounded | Unbounded |
single_assignment |
Both | Ordered | Unbounded | Unbounded |
call |
Ziel | Ordered | Unbounded | Nicht zutreffend |
transformer |
Both | Ordered | Unbounded | 1 |
choice |
Both | Ordered | 10 | 1 |
join |
Both | Ordered | Unbounded | 1 |
multitype_join |
Both | Ordered | 10 | 1 |
timer |
Quelle | Nicht zutreffend | Nicht zutreffend | 1 |
In den folgenden Abschnitten werden die Nachrichtenblocktypen ausführlicher beschrieben.
unbounded_buffer Klasse
Die concurrency::unbounded_buffer stellt eine universelle asynchrone Messaging-Struktur 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.
Example
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 für die Verwendung der Klasse unbounded_buffer finden Sie unter Anleitung: Implementieren verschiedener Produzenten-Verbraucher-Muster.
overwrite_buffer Klasse
Die concurrency::overwrite_buffer ähnelt der Klasse unbounded_buffer , mit dem Unterschied, 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.
Example
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 für die Verwendung der Klasse overwrite_buffer finden Sie unter Anleitung: Implementieren verschiedener Produzenten-Verbraucher-Muster.
single_assignment Klasse
Die concurrency::single_assignment ähnelt der Klasse overwrite_buffer , mit dem Unterschied, 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.
Example
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 für die Verwendung der Klasse single_assignment finden Sie unter Leitfaden: Implementieren von Futures.
call-Klasse
Die concurrency::call fungiert als Nachrichtenempfänger, der bei Empfang von Daten eine Arbeitsfunktion ausführt. 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.
Example
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 call als Objekt Nachrichten in einem separaten Thread verarbeitet, werden in diesem Beispiel auch eine Zählervariable und ein event -Objekt verwendet, um sicherzustellen, dass das call -Objekt alle Nachrichten verarbeitet, bevor die wmain -Funktion zurückkehrt.
// 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 für die Verwendung der Klasse call finden Sie unter Anleitung: Bereitstellen von Arbeitsfunktionen für die Klassen „call“ und „transformer“.
transformer-Klasse
Die concurrency::transformer fungiert sowohl als Empfänger als auch als Sender von Nachrichten. 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 Parameter _PTarget im Konstruktor auf NULLaktivieren, können Sie das Ziel später durch Aufruf der Methode concurrency::link_target aufruft.
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.
Example
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 für die Verwendung der Klasse transformer finden Sie unter Anleitung: Verwenden von Transformatoren in einer Datenpipeline.
choice-Klasse
Die concurrency::choice wählt die erste verfügbare Nachricht aus einer Reihe von Quellen aus. Die choice repräsentiert einen Kontrollflussmechanismus anstelle eines Datenflussmechanismus (das Thema Asynchronous Agents Library beschreibt die Unterschiede zwischen Datenfluss und Kontrollfluss).
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 Klasse choice zusammen mit der Funktion concurrency::receive , um den Kontrollfluss 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.
Example
Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der choice-Klasse veranschaulicht. In diesem Beispiel wird die Funktion concurrency::make_choice 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 nicht garantiert ist, dass die Aufgabe, die die 35th Fibonacci-Zahl berechnet, zuerst beendet wird, kann die Ausgabe dieses Beispiels variieren.
In diesem Beispiel wird die Funktion concurrency::parallel_invoke , um die Fibonacci-Zahlen parallel zu berechnen. Weitere Informationen zu parallel_invokefinden Sie unter Parallel Algorithmen.
Ein vollständiges Beispiel für die Verwendung der Klasse choice finden Sie unter Anleitung: Aus abgeschlossenen Aufgaben auswählen.
Teilnehmen und multitype_join Klassen
Die concurrency::join und concurrency::multitype_join Klassen können Sie warten, bis jedes Mitglied einer Gruppe von Quellen eine Nachricht empfangen hat. 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. Ein greedy join akzeptiert eine Nachricht von jeder seiner Quellen, sobald diese verfügbar ist, bis alle Nachrichten verfügbar sind. Ein nicht gieriger Join 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.
Example
Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der join-Klasse veranschaulicht. In diesem Beispiel wird die Funktion concurrency::make_join verwendet, um ein join -Objekt zu erstellen, das von drei single_assignment -Objekten empfängt. 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 die Funktion concurrency::parallel_invoke , um die Fibonacci-Zahlen parallel zu berechnen. Weitere Informationen zu parallel_invokefinden Sie unter Parallel Algorithmen.
Vollständige Beispiele zur Verwendung der Klasse join finden Sie unter Anleitung: Aus abgeschlossenen Aufgaben auswählen und Leitfaden: Verwenden von „join“, um Deadlocks zu vermeiden.
timer-Klasse
Die concurrency::timer class 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 Parameter _PTarget im Konstruktor auf NULLaktivieren, können Sie das Ziel später durch Aufruf der Methode concurrency::ISource::link_target aufruft.
Ein timer-Objekt kann ein sich wiederholendes oder ein sich nicht wiederholendes Objekt sein. Um einen wiederholten Timer zu erstellen, übergeben Sie true für den Parameter _Repeating , wenn Sie den Konstruktor aufrufen. Andernfalls übergeben Sie false für den Parameter _Repeating , um einen nicht wiederholenden 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. Um ein Timer-Objekt zu starten, rufen Sie die Methode concurrency::timer::start aufruft. Um ein timer -Objekt zu stoppen, zerstören Sie das Objekt oder rufen Sie die Methode concurrency::timer::stop aufruft. Um einen sich wiederholenden Timer anzuhalten, rufen Sie die Methode concurrency::timer::pause aufruft.
Example
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 für die Verwendung der Klasse timer finden Sie unter Anleitung: Senden einer Nachricht in regelmäßigen Abständen.
Nachrichtenfilterung
Wenn Sie ein Meldungsblockobjekt erstellen, können Sie eine Filterfunktion angeben, die bestimmt, ob der Meldungsblock eine Meldung 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 Datenfluss- Programmiermodell, bei dem Komponenten Berechnungen ausführen, wenn sie Daten empfangen. Beispiele für die Verwendung von Filterfunktionen zur Steuerung des Datenflusses in einem Nachrichtenübermittlungsnetzwerk finden Sie unter Anleitung: Verwenden eines Nachrichtenblockfilters, Leitfaden: Erstellen eines Datenflussagentenund Leitfaden: Erstellen eines Bildverarbeitungsnetzwerks.
Nachricht Reservierung
Nachricht Reservierung ermöglicht es einer Nachrichtenblock, eine Nachricht für die spätere 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. Eine Anleitung zum Erstellen eines benutzerdefinierten Meldungsblocktyps finden Sie unter Leitfaden: Erstellen eines benutzerdefinierten Meldungsblocks.