Blocchi dei messaggi asincroni
La libreria agenti fornisce diversi tipi di blocchi di messaggi che consentono di propagare messaggi tra i componenti dell'applicazione in modo thread-safe. Questi tipi di blocco di messaggi vengono spesso usati con le varie routine di passaggio dei messaggi, ad esempio concurrency::send, concurrency::asend, concurrency::receive e concurrency::try_receive. Per altre informazioni sul passaggio di routine definite dalla libreria agenti, vedere Funzioni di passaggio dei messaggi.
Sezioni
Questo argomento include le sezioni seguenti:
Origini e destinazioni
Le origini e le destinazioni sono due importanti partecipanti al passaggio dei messaggi. Un'origine fa riferimento a un endpoint di comunicazione che invia messaggi. Una destinazione fa riferimento a un endpoint di comunicazione che riceve messaggi. È possibile considerare un'origine come un endpoint da cui si legge e una destinazione come endpoint in cui si scrive. Le applicazioni connettono origini e destinazioni insieme per formare reti di messaggistica.
La libreria agenti usa due classi astratte per rappresentare origini e destinazioni: concurrency::ISource e concurrency::ITarget. I tipi di blocco di messaggi che fungono da origini derivano da ISource
; tipi di blocchi di messaggi che fungono da destinazioni derivano da ITarget
. I tipi di blocco di messaggi che fungono da origini e destinazioni derivano sia da che ITarget
da ISource
.
Propagazione dei messaggi
La propagazione dei messaggi è l'atto di inviare un messaggio da un componente a un altro. Quando viene offerto un messaggio, un blocco di messaggi può accettare, rifiutare o posticipare il messaggio. Ogni tipo di blocco di messaggi archivia e trasmette i messaggi in modi diversi. Ad esempio, la unbounded_buffer
classe archivia un numero illimitato di messaggi, la overwrite_buffer
classe archivia un singolo messaggio alla volta e la classe transformer archivia una versione modificata di ogni messaggio. Questi tipi di blocchi di messaggi sono descritti in modo più dettagliato più avanti in questo documento.
Quando un blocco di messaggi accetta un messaggio, può facoltativamente eseguire operazioni e, se il blocco di messaggi è un'origine, passare il messaggio risultante a un altro membro della rete. Un blocco di messaggi può usare una funzione di filtro per rifiutare i messaggi che non desidera ricevere. I filtri sono descritti in modo più dettagliato più avanti in questo argomento, nella sezione Filtro messaggi. Un blocco di messaggi che posticipa un messaggio può riservare tale messaggio e utilizzarlo in un secondo momento. La prenotazione dei messaggi viene descritta in modo più dettagliato più avanti in questo argomento, nella sezione Prenotazione messaggi.
La libreria agenti consente ai blocchi di messaggi di passare in modo asincrono o sincrono i messaggi. Quando si passa un messaggio a un blocco di messaggi in modo sincrono, ad esempio usando la send
funzione , il runtime blocca il contesto corrente finché il blocco di destinazione non accetta o rifiuta il messaggio. Quando si passa un messaggio a un blocco di messaggi in modo asincrono, ad esempio usando la asend
funzione , il runtime offre il messaggio alla destinazione e se la destinazione accetta il messaggio, il runtime pianifica un'attività asincrona che propaga il messaggio al ricevitore. Il runtime usa attività leggere per propagare i messaggi in modo cooperativo. Per altre informazioni sulle attività leggere, vedere Utilità di pianificazione.
Le applicazioni connettono origini e destinazioni insieme per formare reti di messaggistica. In genere, si collega la rete e si chiama send
o asend
per passare i dati alla rete. Per connettere un blocco di messaggi di origine a una destinazione, chiamare il metodo concurrency::ISource::link_target . Per disconnettere un blocco di origine da una destinazione, chiamare il metodo concurrency::ISource::unlink_target . Per disconnettere un blocco di origine da tutte le destinazioni, chiamare il metodo concurrency::ISource::unlink_targets . Quando uno dei tipi di blocco di messaggi predefiniti lascia l'ambito o viene eliminato definitivamente, si disconnette automaticamente da qualsiasi blocco di destinazione. Alcuni tipi di blocchi di messaggi limitano il numero massimo di destinazioni in cui è possibile scrivere. Nella sezione seguente vengono descritte le restrizioni applicabili ai tipi di blocchi di messaggi predefiniti.
Panoramica dei tipi di blocchi di messaggi
La tabella seguente descrive brevemente il ruolo dei tipi importanti di blocchi di messaggi.
unbounded_buffer
Archivia una coda di messaggi.
overwrite_buffer
Archivia un messaggio in cui è possibile scrivere e leggere più volte.
single_assignment
Archivia un messaggio che può essere scritto una sola volta e letto da più volte.
call
Esegue il lavoro quando riceve un messaggio.
trasformatore
Esegue il lavoro quando riceve i dati e invia il risultato di tale operazione a un altro blocco di destinazione. La transformer
classe può agire su tipi di input e output diversi.
choice
Seleziona il primo messaggio disponibile da un set di origini.
join e join multitipo
Attendere che tutti i messaggi vengano ricevuti da un set di origini e quindi combinare i messaggi in un messaggio per un altro blocco di messaggi.
temporizzatore
Invia un messaggio a un blocco di destinazione a intervalli regolari.
Questi tipi di blocchi di messaggi presentano caratteristiche diverse che le rendono utili per situazioni diverse. Ecco alcune delle caratteristiche seguenti:
Tipo di propagazione: indica se il blocco di messaggi funge da origine di dati, da un ricevitore di dati o da entrambi.
Ordinamento dei messaggi: indica se il blocco di messaggi mantiene l'ordine originale in cui i messaggi vengono inviati o ricevuti. Ogni tipo di blocco di messaggi predefinito mantiene l'ordine originale in cui invia o riceve messaggi.
Numero di origini: numero massimo di origini da cui il blocco di messaggi può leggere.
Numero di destinazioni: numero massimo di destinazioni in cui il blocco di messaggi può scrivere.
Nella tabella seguente viene illustrato il modo in cui queste caratteristiche sono correlate ai vari tipi di blocchi di messaggi.
Tipo di blocco di messaggi | Tipo di propagazione (origine, destinazione o entrambi) | Ordinamento dei messaggi (ordinato o non ordinato) | Conteggio delle origini | Conteggio di destinazione |
---|---|---|---|---|
unbounded_buffer |
Entrambi | Quantità ordinata | Senza limiti | Senza limiti |
overwrite_buffer |
Entrambi | Quantità ordinata | Senza limiti | Senza limiti |
single_assignment |
Entrambi | Quantità ordinata | Senza limiti | Senza limiti |
call |
Destinazione | Quantità ordinata | Senza limiti | Non applicabile |
transformer |
Entrambi | Quantità ordinata | Senza limiti | 1 |
choice |
Entrambi | Quantità ordinata | 10 | 1 |
join |
Entrambi | Quantità ordinata | Senza limiti | 1 |
multitype_join |
Entrambi | Quantità ordinata | 10 | 1 |
timer |
Origine | Non applicabile | Non applicabile | 1 |
Le sezioni seguenti descrivono i tipi di blocchi di messaggi in modo più dettagliato.
Classe unbounded_buffer
La classe concurrency::unbounded_buffer rappresenta una struttura di messaggistica asincrona per utilizzo generico. Questa classe archivia una coda di messaggi FIFO (First In, First Out) che possono essere letti da più destinazioni o in cui possono scrivere più origini. Quando una destinazione riceve un messaggio da un unbounded_buffer
oggetto , tale messaggio viene rimosso dalla coda di messaggi. Pertanto, anche se un unbounded_buffer
oggetto può avere più destinazioni, ogni messaggio riceverà una sola destinazione. La classe unbounded_buffer
è utile quando si vogliono passare più messaggi a un altro componente e tale componente deve ricevere ogni messaggio.
Esempio
Nell'esempio seguente viene illustrata la struttura di base di come usare la unbounded_buffer
classe . Questo esempio invia tre valori a un unbounded_buffer
oggetto e quindi legge tali valori dallo stesso oggetto.
// 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;
}
Nell'esempio viene prodotto l'output seguente:
334455
Per un esempio completo che illustra come usare la unbounded_buffer
classe , vedere Procedura: Implementare vari modelli producer-consumer.
Classe overwrite_buffer
La classe concurrency::overwrite_buffer è simile alla unbounded_buffer
classe , ad eccezione del fatto che un overwrite_buffer
oggetto archivia un solo messaggio. Inoltre, quando una destinazione riceve un messaggio da un overwrite_buffer
oggetto , tale messaggio non viene rimosso dal buffer. Pertanto, più destinazioni riceveranno una copia del messaggio.
La overwrite_buffer
classe è utile quando si desidera passare più messaggi a un altro componente, ma tale componente richiede solo il valore più recente. Questa classe è utile anche quando si vuole trasmettere un messaggio a più componenti.
Esempio
Nell'esempio seguente viene illustrata la struttura di base di come usare la overwrite_buffer
classe . Questo esempio invia tre valori a un overwrite _buffer
oggetto e quindi legge il valore corrente dallo stesso oggetto tre volte. Questo esempio è simile all'esempio per la unbounded_buffer
classe . Tuttavia, la overwrite_buffer
classe archivia un solo messaggio. Inoltre, il runtime non rimuove il messaggio da un overwrite_buffer
oggetto dopo la lettura.
// 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;
}
Nell'esempio viene prodotto l'output seguente:
555555
Per un esempio completo che illustra come usare la overwrite_buffer
classe , vedere Procedura: Implementare vari modelli producer-consumer.
Classe single_assignment
La classe concurrency::single_assignment è simile alla overwrite_buffer
classe , ad eccezione del fatto che un single_assignment
oggetto può essere scritto una sola volta. Come per la classe overwrite_buffer
, quando da una destinazione riceve un messaggio da un oggetto single_assignment
, il messaggio viene rimosso dalla coda di messaggi. Pertanto, più destinazioni riceveranno una copia del messaggio. La single_assignment
classe è utile quando si vuole trasmettere un messaggio a più componenti.
Esempio
Nell'esempio seguente viene illustrata la struttura di base di come usare la single_assignment
classe . Questo esempio invia tre valori a un single_assignment
oggetto e quindi legge il valore corrente dallo stesso oggetto tre volte. Questo esempio è simile all'esempio per la overwrite_buffer
classe . Anche se entrambe le overwrite_buffer
classi e single_assignment
archiviano un singolo messaggio, la single_assignment
classe può essere scritta una sola volta.
// 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;
}
Nell'esempio viene prodotto l'output seguente:
333333
Per un esempio completo che illustra come usare la single_assignment
classe , vedere Procedura dettagliata: Implementazione di futures.
Classe call
La classe concurrency::call funge da ricevitore di messaggi che esegue una funzione di lavoro quando riceve i dati. Questa funzione di lavoro può essere un'espressione lambda, un oggetto funzione o un puntatore a funzione. Un call
oggetto si comporta in modo diverso rispetto a una normale chiamata di funzione perché agisce in parallelo ad altri componenti che inviano messaggi. Se un call
oggetto funziona quando riceve un messaggio, aggiunge tale messaggio a una coda. Ogni call
oggetto elabora i messaggi in coda nell'ordine in cui vengono ricevuti.
Esempio
Nell'esempio seguente viene illustrata la struttura di base di come usare la call
classe . In questo esempio viene creato un call
oggetto che stampa ogni valore ricevuto nella console. L'esempio invia quindi tre valori all'oggetto call
. Poiché l'oggetto call
elabora i messaggi in un thread separato, in questo esempio viene utilizzata anche una variabile contatore e un oggetto evento per garantire che l'oggetto call
elabori tutti i messaggi prima che la wmain
funzione restituisca.
// 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();
}
Nell'esempio viene prodotto l'output seguente:
334455
Per un esempio completo che illustra come usare la call
classe , vedere Procedura: Fornire funzioni di lavoro alle classi di chiamata e trasformatore.
Classe transformer
La classe concurrency::transformer funge sia da ricevitore di messaggi che come mittente del messaggio. La transformer
classe è simile alla call
classe perché esegue una funzione di lavoro definita dall'utente quando riceve i dati. Tuttavia, la transformer
classe invia anche il risultato della funzione di lavoro agli oggetti ricevitore. Analogamente a un call
oggetto, un transformer
oggetto agisce in parallelo ad altri componenti che inviano messaggi. Se un transformer
oggetto funziona quando riceve un messaggio, aggiunge tale messaggio a una coda. Ogni transformer
oggetto elabora i messaggi in coda nell'ordine in cui vengono ricevuti.
La transformer
classe invia il messaggio a una destinazione. Se si imposta il _PTarget
parametro nel costruttore su NULL
, è possibile specificare successivamente la destinazione chiamando il metodo concurrency::link_target .
A differenza di tutti gli altri tipi di blocchi di messaggi asincroni forniti dalla libreria agenti, la transformer
classe può agire su tipi di input e output diversi. Questa possibilità di trasformare i dati da un tipo a un altro rende la transformer
classe un componente chiave in molte reti simultanee. Inoltre, è possibile aggiungere funzionalità parallele con granularità fine nella funzione di lavoro di un transformer
oggetto .
Esempio
Nell'esempio seguente viene illustrata la struttura di base di come usare la transformer
classe . In questo esempio viene creato un transformer
oggetto che moltiplica ogni valore di input int
di 0,33 per produrre un double
valore come output. L'esempio riceve quindi i valori trasformati dallo stesso transformer
oggetto e li stampa nella console.
// 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;
}
Nell'esempio viene prodotto l'output seguente:
10.8914.5218.15
Per un esempio completo che illustra come usare la transformer
classe , vedere Procedura: Usare il trasformatore in una pipeline di dati.
Classe choice
La classe concurrency::choice seleziona il primo messaggio disponibile da un set di origini. La choice
classe rappresenta un meccanismo di flusso di controllo anziché un meccanismo di flusso di dati (l'argomento Libreria degli agenti asincroni descrive le differenze tra il flusso di dati e il flusso di controllo).
La lettura da un oggetto choice è simile alla chiamata della funzione WaitForMultipleObjects
API Di Windows quando il bWaitAll
parametro è impostato su FALSE
. Tuttavia, la choice
classe associa i dati all'evento stesso anziché a un oggetto di sincronizzazione esterno.
In genere, si usa la choice
classe insieme alla funzione concurrency::receive per guidare il flusso di controllo nell'applicazione. Usare la choice
classe quando è necessario selezionare tra buffer di messaggi con tipi diversi. Usare la single_assignment
classe quando è necessario selezionare tra buffer di messaggi con lo stesso tipo.
L'ordine in cui si collegano le origini a un choice
oggetto è importante perché può determinare quale messaggio è selezionato. Si consideri ad esempio il caso in cui si collegano più buffer di messaggi che contengono già un messaggio a un choice
oggetto . L'oggetto choice
seleziona il messaggio dalla prima origine a cui è collegato. Dopo aver collegato tutte le origini, l'oggetto choice
mantiene l'ordine in cui ogni origine riceve un messaggio.
Esempio
Nell'esempio seguente viene illustrata la struttura di base di come usare la choice
classe . In questo esempio viene usata la funzione concurrency::make_choice per creare un choice
oggetto che seleziona tra tre blocchi di messaggi. L'esempio calcola quindi vari numeri di Fibonacci e archivia ogni risultato in un blocco di messaggi diverso. Nell'esempio viene quindi stampato nella console un messaggio basato sull'operazione che è stata completata per prima.
// 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;
}
}
Questo esempio produce l'output di esempio seguente:
fib35 received its value first. Result = 9227465
Poiché l'attività che calcola il numero 35di Fibonacci non è garantita per la prima fine, l'output di questo esempio può variare.
In questo esempio viene usato l'algoritmo concurrency::p arallel_invoke per calcolare i numeri di Fibonacci in parallelo. Per altre informazioni su parallel_invoke
, vedere Algoritmi paralleli.
Per un esempio completo che illustra come usare la choice
classe , vedere Procedura: Selezionare tra le attività completate.
join e classi multitype_join
Le classi concurrency::join e concurrency::multitype_join consentono di attendere che ogni membro di un set di origini riceva un messaggio. La join
classe agisce sugli oggetti di origine con un tipo di messaggio comune. La multitype_join
classe agisce sugli oggetti di origine che possono avere tipi di messaggio diversi.
La lettura da un join
oggetto o multitype_join
è simile alla chiamata della funzione WaitForMultipleObjects
API Di Windows quando il bWaitAll
parametro è impostato su TRUE
. Tuttavia, proprio come un choice
oggetto join
e multitype_join
gli oggetti usano un meccanismo di evento che associa i dati all'evento stesso anziché a un oggetto di sincronizzazione esterno.
La lettura da un join
oggetto produce un oggetto std::vector . La lettura da un multitype_join
oggetto produce un oggetto std::tuple . Gli elementi vengono visualizzati in questi oggetti nello stesso ordine dei buffer di origine corrispondenti sono collegati all'oggetto join
o multitype_join
. Poiché l'ordine in cui si collegano i buffer di origine a un join
oggetto o multitype_join
è associato all'ordine degli elementi nell'oggetto o tuple
risultantevector
, è consigliabile non scollegare un buffer di origine esistente da un join. Questa operazione può comportare un comportamento non specificato.
Greedy Versus Non-Greedy Joins
Le join
classi e multitype_join
supportano il concetto di join greedy e non greedy. Un join greedy accetta un messaggio da ognuna delle origini quando i messaggi diventano disponibili fino a quando non sono disponibili tutti i messaggi. Un join non greedy riceve messaggi in due fasi. Prima di tutto, un join non greedy attende fino a quando non viene offerto un messaggio da ognuna delle relative origini. In secondo luogo, dopo che tutti i messaggi di origine sono disponibili, un join non greedy tenta di riservare ognuno di questi messaggi. Se può riservare ogni messaggio, utilizza tutti i messaggi e li propaga alla destinazione. In caso contrario, rilascia o annulla le prenotazioni dei messaggi e attende di nuovo che ogni origine riceva un messaggio.
I join Greedy offrono prestazioni migliori rispetto ai join non greedy perché accettano immediatamente i messaggi. Tuttavia, in rari casi, i join greedy possono causare deadlock. Usare un join non greedy quando sono presenti più join che contengono uno o più oggetti di origine condivisi.
Esempio
Nell'esempio seguente viene illustrata la struttura di base di come usare la join
classe . In questo esempio viene usata la funzione concurrency::make_join per creare un join
oggetto che riceve da tre single_assignment
oggetti. In questo esempio vengono calcolati vari numeri di Fibonacci, ogni risultato viene archiviato in un oggetto diverso single_assignment
e quindi viene stampato nella console ogni risultato contenuto dall'oggetto join
. Questo esempio è simile all'esempio per la choice
classe , ad eccezione del fatto che la join
classe attende che tutti i blocchi di messaggi di origine ricevano un messaggio.
// 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;
}
Nell'esempio viene prodotto l'output seguente:
fib35 = 9227465fib37 = 24157817half_of_fib42 = 1.33957e+008
In questo esempio viene usato l'algoritmo concurrency::p arallel_invoke per calcolare i numeri di Fibonacci in parallelo. Per altre informazioni su parallel_invoke
, vedere Algoritmi paralleli.
Per esempi completi che illustrano come usare la join
classe , vedere Procedura: Selezionare tra le attività completate e Procedura dettagliata: Uso di join per impedire deadlock.
Classe timer
La classe concurrency::timer funge da origine messaggio. Un timer
oggetto invia un messaggio a una destinazione dopo che è trascorso un periodo di tempo specificato. La timer
classe è utile quando è necessario ritardare l'invio di un messaggio o inviare un messaggio a intervalli regolari.
La timer
classe invia il messaggio a una sola destinazione. Se si imposta il _PTarget
parametro nel costruttore su NULL
, è possibile specificare successivamente la destinazione chiamando il metodo concurrency::ISource::link_target .
Un timer
oggetto può essere ripetuto o non ripetuto. Per creare un timer ripetuto, passare true
per il _Repeating
parametro quando si chiama il costruttore. In caso contrario, passare false
per il _Repeating
parametro per creare un timer non ripetuto. Se il timer viene ripetuto, invia lo stesso messaggio alla destinazione dopo ogni intervallo.
La libreria agenti crea timer
oggetti nello stato non avviato. Per avviare un oggetto timer, chiamare il metodo concurrency::timer::start . Per arrestare un timer
oggetto, eliminare definitivamente l'oggetto o chiamare il metodo concurrency::timer::stop . Per sospendere un timer ripetuto, chiamare il metodo concurrency::timer::p ause .
Esempio
Nell'esempio seguente viene illustrata la struttura di base di come usare la timer
classe . Nell'esempio vengono utilizzati oggetti e call
per segnalare timer
lo stato di avanzamento di un'operazione lunga.
// 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;
}
Questo esempio produce l'output di esempio seguente:
Computing fib(42)..................................................result is 267914296
Per un esempio completo che illustra come usare la timer
classe , vedere Procedura: Inviare un messaggio a un intervallo regolare.
Filtro messaggi
Quando si crea un oggetto blocco di messaggi, è possibile fornire una funzione di filtro che determina se il blocco di messaggi accetta o rifiuta un messaggio. Una funzione di filtro è un modo utile per garantire che un blocco di messaggi riceva solo determinati valori.
Nell'esempio seguente viene illustrato come creare un unbounded_buffer
oggetto che usa una funzione di filtro per accettare solo numeri pari. L'oggetto unbounded_buffer
rifiuta numeri dispari e pertanto non propaga numeri dispari ai blocchi di destinazione.
// 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;
}
}
Nell'esempio viene prodotto l'output seguente:
0 2 4 6 8
Una funzione di filtro può essere una funzione lambda, un puntatore a funzione o un oggetto funzione. Ogni funzione di filtro accetta una delle forme seguenti.
bool (T)
bool (T const &)
Per eliminare la copia non necessaria dei dati, utilizzare la seconda maschera quando si dispone di un tipo di aggregazione propagato per valore.
Il filtro dei messaggi supporta il modello di programmazione del flusso di dati, in cui i componenti eseguono calcoli quando ricevono dati. Per esempi che usano funzioni di filtro per controllare il flusso di dati in una rete di passaggio di messaggi, vedere Procedura: Usare un filtro di blocco messaggi, Procedura dettagliata: Creazione di un agente di flussi di dati e Procedura dettagliata: Creazione di una rete di elaborazione immagini.
Prenotazione messaggi
La prenotazione dei messaggi consente a un blocco di messaggi di riservare un messaggio per un uso successivo. In genere, la prenotazione dei messaggi non viene usata direttamente. Tuttavia, la comprensione della prenotazione dei messaggi consente di comprendere meglio il comportamento di alcuni dei tipi di blocchi di messaggi predefiniti.
Si considerino join non greedy e greedy. Entrambe queste usano la prenotazione dei messaggi per riservare i messaggi per usarli in un secondo momento. Descritto in precedenza, un join non greedy riceve messaggi in due fasi. Durante la prima fase, un oggetto non greedy join
attende che ognuna delle relative origini riceva un messaggio. Un join non greedy tenta quindi di riservare ognuno di questi messaggi. Se può riservare ogni messaggio, utilizza tutti i messaggi e li propaga alla destinazione. In caso contrario, rilascia o annulla le prenotazioni dei messaggi e attende di nuovo che ogni origine riceva un messaggio.
Un join greedy, che legge anche i messaggi di input da una serie di origini, usa la prenotazione dei messaggi per leggere messaggi aggiuntivi mentre attende di ricevere un messaggio da ogni origine. Si consideri, ad esempio, un join greedy che riceve messaggi dai blocchi A
di messaggi e B
. Se il join greedy riceve due messaggi da B ma non ha ancora ricevuto un messaggio da A
, il join greedy salva l'identificatore univoco del messaggio per il secondo messaggio da B
. Dopo che il join greedy riceve un messaggio da A
e propaga questi messaggi, usa l'identificatore del messaggio salvato per verificare se il secondo messaggio da B
è ancora disponibile.
È possibile usare la prenotazione dei messaggi quando si implementano tipi di blocchi di messaggi personalizzati. Per un esempio su come creare un tipo di blocco di messaggi personalizzato, vedere Procedura dettagliata: Creazione di un blocco di messaggi personalizzato.