Partilhar via


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 modelos C++ que promove um modelo de programação baseado em atores e passagem de mensagens em processo para tarefas de fluxo de dados e pipelining de grão grosso. A Biblioteca de Agentes baseia-se nos componentes de agendamento e gerenciamento de recursos do Concurrency Runtime.

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 em fluxo de dados em vez de fluxo de controle. O fluxo de dados refere-se a um modelo de programação onde 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 onde os cálculos são feitos em uma ordem predeterminada.

O modelo de programação de fluxo de dados está relacionado ao conceito de passagem de mensagens, onde componentes independentes de um programa se comunicam entre eles enviando mensagens.

A Biblioteca de Agentes é composta por três componentes: agentes assíncronos, blocos de mensagens assíncronas e funções de passagem de mensagens. Os agentes mantêm o estado e usam blocos de mensagens e funções de passagem de mensagens para se comunicar entre si e com componentes externos. As funções de passagem de mensagens permitem que os agentes enviem e recebam mensagens de e para os componentes externos. Os blocos de mensagens assíncronas armazenam 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 mensagens 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 agent1o . As setas tracejadas representam o fluxo de dados entre agentes. As setas sólidas conectam os agentes aos blocos de mensagens nos quais eles escrevem ou leem.

Os componentes da Biblioteca de agentes.

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

O modelo de programação do agente tem várias vantagens sobre outros mecanismos de simultaneidade e sincronização, por exemplo, eventos. Uma vantagem é que, ao usar a passagem de mensagens para transmitir alterações de estado entre objetos, você pode isolar o acesso a recursos compartilhados e, assim, melhorar a escalabilidade. Uma vantagem da passagem de mensagens é que ela vincula a sincronização aos dados em vez de vinculá-los 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 tiver várias operações que devem se comunicar entre si de forma assíncrona. Blocos de mensagens e funções de passagem de mensagens permitem escrever aplicativos paralelos sem exigir mecanismos de sincronização, como bloqueios. Isso permite que você se concentre na lógica do aplicativo.

O modelo de programação de agente é frequentemente usado para criar pipelines de dados ou redes. Um pipeline de dados é uma série de componentes, cada um dos quais executa uma tarefa específica que contribui para um objetivo maior. Cada componente em um pipeline de fluxo de dados executa trabalho quando recebe uma mensagem 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 Biblioteca de Padrões Paralelos (PPL).

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

Este exemplo produz a seguinte saída:

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 dos agentes assíncronos na resolução de tarefas de computação maiores.

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

Funções de passagem de mensagens
Descreve as várias rotinas de passagem de mensagens fornecidas pela Biblioteca de agentes.

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

Como Fornecer Funções de Operação para as Classes de Chamada e Transformador
Ilustra várias maneiras de fornecer funções de trabalho para as classes concurrency::call e concurrency::transformer .

Como usar o 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 em 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 Concorrência
Descreve o Concurrency Runtime, que simplifica a programação paralela e contém links para tópicos relacionados.