Biblioteca de Agentes Assíncronos

A Biblioteca de Agentes Assíncronos (ou apenas Biblioteca de Agentes) fornece um modelo de programação que permite aumentar a robustez do desenvolvimento de aplicativos habilitados para simultaneidade. A Biblioteca de Agentes é uma biblioteca de modelo C++ que promove um modelo de programação baseado em ator e passagem de mensagens em processo para tarefas de pipeline e fluxo de dados de alta granularidade. A Biblioteca de Agentes baseia-se nos componentes de agendamento e gerenciamento de recursos do Runtime de Simultaneidade.

Modelo de Programação

A Biblioteca de Agentes fornece alternativas ao estado compartilhado, permitindo que você conecte componentes isolados por meio de um modelo de comunicação assíncrono baseado no fluxo de dados em vez de no fluxo de controle. O fluxo de dados refere-se a um modelo de programação em que os cálculos são feitos quando todos os dados necessários estão disponíveis. O fluxo de controle refere-se a um modelo de programação em que os cálculos são feitos em uma ordem predeterminada.

O modelo de programação de fluxo de dados está relacionado ao conceito de transmissão de mensagens, em que componentes independentes de um programa se comunicam uns com os outros pelo envio de mensagens.

A Biblioteca de Agentes é composta por três componentes: agentes assíncronos, blocos de mensagens assíncronas e funções de passagem de mensagem. Os agentes mantêm o estado e usam blocos de mensagens e funções de passagem de mensagens para comunicação entre si e com componentes externos. As funções de passagem de mensagem permitem que os agentes enviem e recebam mensagens de e para os componentes externos. Os blocos de mensagens assíncronas contêm mensagens e permitem que os agentes se comuniquem de maneira sincronizada.

A ilustração a seguir mostra como dois agentes usam blocos de mensagens e funções de passagem de mensagem para se comunicar. Nesta ilustração, agent1 envia uma mensagem para agent2 usando a função concurrency::send e um objeto concurrency::unbounded_buffer. agent2 usa a função concurrency::receive para ler a mensagem. agent2 usa o mesmo método para enviar uma mensagem para agent1. As setas tracejadas representam o fluxo de dados entre agentes. As setas sólidas conectam os agentes aos blocos de mensagem dos quais eles gravam ou leem.

The components of the Agents Library.

Um exemplo de código que implementa essa ilustração é mostrado posteriormente neste tópico.

O modelo de programação do agente tem várias vantagens em relação a outros mecanismos de simultaneidade e sincronização, por exemplo, eventos. Uma vantagem é que, ao usar a passagem de mensagem para transmitir alterações de estado entre objetos, você pode isolar o acesso aos recursos compartilhados e, assim, melhorar a escalabilidade. Uma vantagem da passagem de mensagem é que ela vincula a sincronização aos dados em vez de vinculá-la a um objeto de sincronização externo. Isso simplifica a transmissão de dados entre componentes e pode eliminar erros de programação em seus aplicativos.

Quando Usar a Biblioteca de Agentes

Use a Biblioteca de Agentes quando você tiver várias operações que devem se comunicar entre si de modo assíncrono. Os blocos de mensagens e as funções de passagem de mensagem permitem que você escreva aplicativos paralelos sem a necessidade de mecanismos de sincronização, como bloqueios. Isso permite que você se concentre na lógica do aplicativo.

O modelo de programação do agente geralmente é usado para criar pipelines de dados ou redes. Um pipeline de dados é uma série de componentes, e cada um executa uma tarefa específica que contribui para um objetivo maior. Todos os componentes em um pipeline de fluxo de dados realizam trabalhos ao receber mensagens de outro componente. O resultado desse trabalho é passado para outros componentes no pipeline ou na rede. Os componentes podem usar uma funcionalidade de simultaneidade mais refinada de outras bibliotecas, por exemplo, a PPL (Biblioteca de Padrões Paralelos).

Exemplo

O exemplo a seguir implementa a ilustração mostrada anteriormente neste tópico.

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

Esse exemplo gera a saída a seguir:

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

Os tópicos a seguir descrevem a funcionalidade usada neste exemplo.

Agentes assíncronos
Descreve a função de agentes assíncronos na resolução de tarefas de computação maiores.

Blocos de mensagens assíncronos
Descreve os vários tipos de bloco de mensagem fornecidos pela Biblioteca de Agentes.

Funções de transmissão de mensagem
Descreve as várias rotinas de passagem de mensagem fornecidas pela Biblioteca de Agentes.

Como implementar vários padrões de produtor-consumidor
Descreve como implementar o padrão produtor-consumidor em seu aplicativo.

Como fornecer funções de trabalho para as classes call e transformer
Ilustra várias maneiras de fornecer funções de trabalho para as classes concurrency::call e concurrency::transformer.

Como usar transformador em um pipeline de dados
Mostra como usar a classe concurrency::transformer em um pipeline de dados.

Como selecionar entre tarefas concluídas
Mostra como usar as classes concurrency::choice e concurrency::join para selecionar a primeira tarefa para concluir um algoritmo de pesquisa.

Como enviar uma mensagem a um intervalo regular
Mostra como usar a classe concurrency::timer para enviar uma mensagem em um intervalo regular.

Como usar um filtro de bloco de mensagens
Demonstra como usar um filtro para habilitar um bloco de mensagens assíncronas para aceitar ou rejeitar mensagens.

Biblioteca de padrões paralelos (PPL)
Descreve como usar vários padrões paralelos, como algoritmos paralelos, em seus aplicativos.

Runtime de Simultaneidade
Descreve o Runtime de Simultaneidade, que simplifica a programação paralela e contém links para tópicos relacionados.