Condividi tramite


Libreria di agenti asincroni

La libreria di agenti asincroni, o semplicemente libreria di agenti, fornisce un modello di programmazione che consente di migliorare l'affidabilità dello sviluppo di applicazioni basate sulla concorrenza. La libreria di agenti è una libreria di modelli di C++ che promuove un modello di programmazione basato su attori e un passaggio dei messaggi in-process per le attività di pipelining o per un flusso di dati meno accurato. La libreria di agenti si basa sulla pianificazione e sui componenti di gestione delle risorse del runtime di concorrenza.

Modello di programmazione

La libreria di agenti offre alternative allo stato condiviso consentendo la connessione di componenti isolati attraverso un modello di comunicazione asincrona basato sul flusso di dati anziché sul flusso di controllo. Il flusso di dati si riferisce a un modello di programmazione in cui i calcoli vengono eseguiti quando sono disponibili tutti i dati necessari; il flusso di controllo si riferisce a un modello di programmazione in cui i calcoli vengono eseguiti in un ordine predeterminato.

Il modello di programmazione del flusso di dati è correlato al concetto di passaggio dei messaggi, in base al quale i componenti indipendenti di un programma comunicano con un altro programma inviando messaggi.

La libreria di agenti è composta da tre componenti: agenti asincroni, blocchi dei messaggi asincroni e funzioni di passaggio dei messaggi. Gli agenti mantengono lo stato e utilizzano i blocchi dei messaggi e le funzioni di passaggio dei messaggi per comunicare tra loro e con i componenti esterni. Le funzioni di passaggio dei messaggi consentono agli agenti di inviare e ricevere messaggi da componenti esterni. I blocchi dei messaggi asincroni contengono i messaggi e consentono agli agenti di comunicare in modo sincronizzato.

L'illustrazione seguente mostra come due agenti utilizzano blocchi dei messaggi e le funzioni di passaggio dei messaggi per comunicare. In questa illustrazione agent1 invia un messaggio a agent2 tramite la funzione Concurrency::send e un oggetto Concurrency::unbounded_buffer. agent2 utilizza la funzione Concurrency::receive per leggere il messaggio. agent2 utilizza lo stesso metodo per inviare un messaggio a agent1. Le frecce tratteggiate rappresentano il flusso di dati tra gli agenti. Le frecce continue connettono gli agenti ai blocchi dei messaggi in cui scrivono o da cui leggono.

Componenti della libreria di agenti

Un esempio di codice che implementa questa illustrazione viene mostrato più avanti in questo argomento.

Il modello di programmazione dell'agente offre diversi vantaggi rispetto agli altri meccanismi di concorrenza e sincronizzazione, ad esempio gli eventi. Un vantaggio è costituito dal fatto che utilizzando il passaggio dei messaggi per trasmettere le modifiche di stato tra gli oggetti, è possibile isolare l'accesso alle risorse condivise migliorando pertanto la scalabilità. Uno dei vantaggi che derivano dal passaggio dei messaggi risiede nel fatto che la sincronizzazione è collegata ai dati anziché a un oggetto di sincronizzazione esterno. In questo modo viene semplificata la trasmissione dei dati tra componenti eliminando gli errori di programmazione nelle applicazioni.

Quando utilizzare la libreria di agenti

Utilizzare la libreria di agenti quando si dispone di più operazioni che devono comunicare tra loro in modo asincrono. I blocchi dei messaggi e le funzioni di passaggio dei messaggi consentono di scrivere applicazioni parallele senza la necessità dei meccanismi di sincronizzazione come i blocchi, consentendo di concentrarsi sulla logica dell'applicazione.

Il modello di programmazione dell'agente viene spesso utilizzato per creare pipeline di dati o reti. Una pipeline di dati è costituita da una serie di componenti, ognuno dei quali esegue un'attività specifica che contribuisce a un obiettivo più grande. Ogni componente di una pipeline del flusso di dati esegue un lavoro quando riceve un messaggio da un altro componente. Il risultato di tale lavoro viene passato agli altri componenti della pipeline o della rete. I componenti possono utilizzare le funzionalità di concorrenza più accurate presenti nelle altre librerie, ad esempio PPL (Parallel Patterns Library).

Esempio

Nell'esempio seguente viene implementata l'illustrazione mostrata in precedenza in questo argomento.

// basic-agents.cpp
// compile with: /EHsc
#include <agents.h>
#include <string>
#include <iostream>
#include <sstream>

using namespace Concurrency;
using namespace std;

// This agent writes a string to its target and reads an integer
// from its source.
class agent1 : public agent 
{
public:
   explicit agent1(ISource<int>& source, ITarget<wstring>& target)
      : _source(source)
      , _target(target)
   {
   }

protected:
   void run()
   {
      // Send the request.
      wstringstream ss;
      ss << L"agent1: sending request..." << endl;
      wcout << ss.str();

      send(_target, wstring(L"request"));

      // Read the response.
      int response = receive(_source);

      ss = wstringstream();
      ss << L"agent1: received '" << response << L"'." << endl;
      wcout << ss.str();

      // Move the agent to the finished state.
      done();
   }

private:   
   ISource<int>& _source;
   ITarget<wstring>& _target;
};

// This agent reads a string to its source and then writes an integer
// to its target.
class agent2 : public agent 
{
public:
   explicit agent2(ISource<wstring>& source, ITarget<int>& target)
      : _source(source)
      , _target(target)
   {
   }

protected:
   void run()
   {
      // Read the request.
      wstring request = receive(_source);

      wstringstream ss;
      ss << L"agent2: received '" << request << L"'." << endl;
      wcout << ss.str();

      // Send the response.
      ss = wstringstream();
      ss << L"agent2: sending response..." << endl;
      wcout << ss.str();

      send(_target, 42);

      // Move the agent to the finished state.
      done();
   }

private:   
   ISource<wstring>& _source;
   ITarget<int>& _target;
};

int wmain()
{
   // Step 1: Create two message buffers to serve as communication channels
   // between the agents.

   // The first agent writes messages to this buffer; the second
   // agents reads messages from this buffer.
   unbounded_buffer<wstring> buffer1;

   // The first agent reads messages from this buffer; the second
   // agents writes messages to this buffer.
   overwrite_buffer<int> buffer2;

   // Step 2: Create the agents.
   agent1 first_agent(buffer2, buffer1);
   agent2 second_agent(buffer1, buffer2);

   // Step 3: Start the agents. The runtime calls the run method on
   // each agent.
   first_agent.start();
   second_agent.start();

   // Step 4: Wait for both agents to finish.
   agent::wait(&first_agent);
   agent::wait(&second_agent);
}

Questo esempio produce l'output seguente:

agent1: sending request...
agent2: received 'request'.
agent2: sending response...
agent1: received '42'.

Negli argomenti seguenti viene descritta la funzionalità utilizzata in questo esempio.

Argomenti correlati