Compartir a través de


Biblioteca de agentes asincrónicos

La Biblioteca de agentes asincrónicos (o simplemente Biblioteca de agentes) proporciona un modelo de programación que permite mejorar la solidez del desarrollo de aplicaciones con la simultaneidad habilitada. La biblioteca de agentes es una biblioteca de plantillas de C++ que promueve un modelo de programación basado en actores y el paso de mensajes en proceso para tareas genéricas de flujo de datos y canalización. La Biblioteca de agentes se basa en los componentes de programación y administración de recursos del Runtime de simultaneidad.

Modelo de programación

La biblioteca de agentes proporciona alternativas al estado compartido ya que permite conectar componentes aislados a través de un modelo de comunicación asincrónica basado en el flujo de datos y no en el flujo de control. El término flujo de datos hace referencia a un modelo de programación donde se realizan los cálculos cuando están disponibles todos los datos necesarios; el término flujo de control hace referencia a un modelo de programación donde los cálculos se realizan en un orden predeterminado.

El modelo de programación basado en el flujo de datos está relacionado con el concepto paso de mensajes, donde los componentes independientes de un programa comunican entre sí enviándose mensajes.

La Biblioteca de agentes consta de tres componentes: agentes asincrónicos, bloques de mensajes asincrónicos y funciones de paso de mensajes. Los agentes mantienen el estado y utilizan bloques de mensajes así como funciones de paso de mensajes para comunicarse entre sí y con componentes externos. Las funciones de paso de mensajes permiten a los agentes enviar y recibir mensajes de los componentes externos. Los bloques de mensajes asincrónicos contienen mensajes y permiten a los agentes comunicar de manera sincronizada.

En la siguiente ilustración, se muestra cómo dos agentes utilizan bloques de mensajes y funciones de paso de mensajes para comunicar. En esta ilustración, agent1 envía un mensaje a agent2 mediante la función concurrency::send y un objeto concurrency::unbounded_buffer. agent2 usa la función concurrency::receive para leer el mensaje. agent2 utiliza el mismo método para enviar un mensaje a agent1. Las flechas de líneas discontinuas representan el flujo de datos entre los agentes. Las flechas de líneas continuas conectan los agentes con los bloques de mensajes en los que escriben o que leen.

Los componentes de la biblioteca de agentes.

Más adelante en este tema figura un ejemplo de código en el que se implementa esta ilustración.

El modelo de programación basado en agentes ofrece varias ventajas con respecto a otros mecanismos de simultaneidad y sincronización, como eventos. Una de las ventajas consiste en que, al usar el paso de mensajes para transmitir los cambios de estado de un objeto a otro, se puede aislar el acceso a los recursos compartidos y, de este modo, mejorar la escalabilidad. El paso de mensajes ofrece como ventaja que vincula la sincronización a los datos en lugar de vincularla a un objeto de sincronización externo. De este modo, se simplifica la transmisión de datos entre los componentes y se pueden eliminar los errores de programación de las aplicaciones.

Cuándo debe usarse la Biblioteca de agentes

Utilice la Biblioteca de agentes en el caso de varias operaciones que deben comunicar entre sí de forma asincrónica. Los bloques de mensajes y las funciones de paso de mensajes permiten escribir aplicaciones paralelas sin necesidad de mecanismos de sincronización, como bloqueos. De este modo, el usuario podrá centrarse en la lógica de la aplicación.

El modelo de programación basado en agentes suele usarse para crear canalizaciones de datos o redes. Una canalización de datos es una serie de componentes, cada uno de los cuales realiza una tarea concreta que contribuye a lograr un objetivo mayor. Cada componente de una canalización de flujo de datos realiza un determinado trabajo cuando recibe un mensaje de otro componente. El resultado de ese trabajo se pasa a otros componentes de la canalización o red. Los componentes pueden utilizar la funcionalidad de simultaneidad más específica de otras bibliotecas, como la Biblioteca de modelos de procesamiento paralelo (PPL).

Ejemplo

En el siguiente ejemplo, se implementa la ilustración que se muestra anteriormente en este tema.

// 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);
}

Este ejemplo produce el siguiente resultado:

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

En los siguientes temas, se describe la funcionalidad que se usa en este ejemplo.

Agentes asincrónicos
Se describe el rol de los agentes asincrónicos a la hora de resolver tareas de computación mayores.

Bloques de mensajes asincrónicos
Se describen los diversos tipos de bloques de mensajes proporcionados por la Biblioteca de agentes.

Funciones que pasan mensajes
Se describen las diversas rutinas de paso de mensajes proporcionadas por la Biblioteca de agentes.

Procedimiento para implementar varios modelos productor-consumidor
Se describe cómo se implementa el modelo productor-consumidor en la aplicación.

Procedimiento para proporcionar funciones de trabajo a las clases call y transformer
Ilustra varias maneras de proporcionar funciones de trabajo a las clases concurrency::call y concurrency::transformer.

Procedimiento para usar la clase transformer en una canalización de datos
Muestra cómo usar la clase concurrency::transformer en una canalización de datos.

Procedimiento para seleccionar tareas completadas
Muestra cómo usar las clases concurrency::choice y concurrency::join para seleccionar la primera tarea para completar un algoritmo de búsqueda.

Procedimiento para enviar un mensaje a intervalos periódicos
Muestra cómo se usa la clase concurrency::timer para enviar un mensaje de manera periódica.

Procedimiento para usar un filtro de bloque de mensaje
Muestra cómo usar un filtro para permitir que un bloque de mensajes asincrónicos para aceptar o rechazar los mensajes.

Biblioteca de modelos de procesamiento paralelo (PPL)
Se describe cómo utilizar diferentes modelos paralelos, como los algoritmos paralelos, en las aplicaciones.

Runtime de simultaneidad
Se describe el Runtime de simultaneidad, que simplifica la programación en paralelo, y contiene vínculos a los temas relacionados.