Библиотека асинхронных агентов

Библиотека асинхронных агентов (или просто библиотека агентов) предоставляет модель программирования, которая позволяет повысить надежность разработки приложений с поддержкой параллелизма. Библиотека агентов — это библиотека шаблонов C++, поддерживающая модель программирования на основе субъектов и внутрипроцессную передачу сообщений для потока данных с низкой степенью детализации и конвейеризации задач. Библиотека агентов создана на основе компонентов планирования и управления ресурсами среды выполнения с параллелизмом.

Модель программирования

Библиотека агентов предоставляет альтернативы общему состоянию, позволяя подключать изолированные компоненты через модель асинхронной передачи данных, основанной на потоке данных, а не потоке управления. Термином Поток данных обозначается модель программирования, в которой вычисления выполняются, когда доступны все необходимые данные; понятие поток элементов управления обозначает модель программирования, в которой вычисления выполняются в предопределенном порядке.

Модель программирования на основе потока данных связана с понятием передача сообщений, так как в этой модели независимые компоненты программы взаимодействуют друг с другом посредством отправки сообщений.

Библиотека агентов состоит из следующих трех компонентов: асинхронные агенты, асинхронные блоки сообщений и функции передачи сообщений. Агенты поддерживают состояние и используют блоки сообщений и функции передачи сообщений для взаимодействия друг с другом и с внешними компонентами. Функции передачи сообщений позволяют агентам отправлять сообщения внешним компонентам и получать сообщения от них. Асинхронные блоки сообщений содержат сообщения и позволяют агентам взаимодействовать синхронизированно.

На следующем рисунке показано взаимодействие двух агентов с использованием блоков сообщений и функций передачи сообщений. На этом рисунке agent1 отправляет сообщение agent2 с использованием функции Concurrency::send и объекта Concurrency::unbounded_buffer. agent2 использует для прочтения сообщения функцию Concurrency::receive. agent2 использует тот же метод для отправки сообщения agent1. Пунктирными стрелками обозначается поток данных между агентами. Сплошные стрелки соединяют агенты с блоками сообщений, в которые записываются сообщения и из которых они считываются.

Компоненты библиотеки агентов

Пример кода для данной иллюстрации приводится далее в этом разделе.

Модель программирования на основе агентов имеет несколько преимуществ по сравнению с другими механизмами параллелизма и синхронизации, например, механизмом событий. Одним из преимуществ является то, что используя передачу сообщений для обмена данными об изменении состояния между объектами, можно изолировать доступ к общим ресурсам и повысить масштабируемость системы. Преимуществом функции передачи сообщений является то, что она связывает синхронизацию с данными, а не внешним объектом синхронизации. Это упрощает передачу данных между компонентами и позволяет избежать ошибок программирования в приложениях.

Когда следует использовать библиотеку агентов

Библиотеку агентов рекомендуется использовать, если имеется несколько операций, которые должны асинхронно взаимодействовать друг с другом. Блоки сообщений и функции передачи сообщений позволяют записывать параллельные приложения, не требуя механизмов синхронизации (например, блокировок). Это позволяет сосредоточиться на логике приложения.

Модель программирования на основе агентов часто используется для создания конвейеров данных или сетей. Конвейер данных — это ряд компонентов, каждый из которых выполняет конкретную задачу в рамках более крупной цели. Каждый компонент конвейера потока данных выполняет работу, когда получает сообщение от другого компонента. Результат этой работы передается другим компонентам конвейера или сети. Компоненты могут использовать более специфические функции параллелизма из других библиотек, например Библиотека параллельных шаблонов.

Пример

В следующем примере иллюстрация, представленная ранее в этом разделе, реализуется в коде.

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

В данном примере получается следующий результат:

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

В следующих разделах описаны функции, используемые в этом примере.

Связанные разделы