비동기 에이전트 라이브러리

비동기 에이전트 라이브러리(에이전트 라이브러리라고도 함)에서는 동시성 사용 응용 프로그램 개발의 안정성을 향상시킬 수 있는 프로그래밍 모델을 제공합니다. 에이전트 라이브러리는 정교하지 않은 데이터 흐름 및 파이프라인 작업을 위해 행위자 기반 프로그래밍 모델과 in-process 메시지 전달을 촉진하는 C++ 템플릿 라이브러리입니다. 에이전트 라이브러리는 동시성 런타임의 일정 예약 및 리소스 관리 구성 요소를 바탕으로 합니다.

프로그래밍 모델

에이전트 라이브러리는 격리된 구성 요소를 제어 흐름 대신 데이터 흐름을 기반으로 하는 비동기 통신 모델을 통해 연결할 수 있도록 하여 공유 상태에 대한 대안을 제공합니다. 데이터 흐름은 필요한 모든 데이터를 사용할 수 있을 때 계산이 실행되는 프로그래밍 모델을 나타내고 제어 흐름은 미리 결정된 순서로 계산이 실행되는 프로그래밍 모델을 나타냅니다.

데이터 흐름 프로그래밍 모델은 프로그램의 개별 구성 요소가 메시지를 보내 다른 구성 요소와 통신하는 메시지 전달 개념과 관련되어 있습니다.

에이전트 라이브러리는 비동기 에이전트, 비동기 메시지 블록 및 메시지 전달 함수라는 세 가지 구성 요소로 구성됩니다. 에이전트는 상태를 유지 관리하고 메시지 블록 및 메시지 전달 함수를 사용하여 내부의 다른 구성 요소 및 외부 구성 요소와 통신합니다. 메시지 전달 함수는 에이전트가 외부 구성 요소와 메시지를 주고 받을 수 있도록 합니다. 비동기 메시지 블록은 메시지를 보관하고 에이전트가 동기화된 방식으로 통신할 수 있도록 합니다.

다음 그림에서는 두 에이전트가 메시지 블록 및 메시지 전달 함수를 사용하여 통신하는 방법을 보여 줍니다. 이 그림에서 agent1Concurrency::send 함수 및 Concurrency::unbounded_buffer 개체를 사용하여 agent2에 메시지를 보냅니다. agent2Concurrency::receive 함수를 사용하여 메시지를 읽습니다. agent2는 같은 메서드를 사용하여 agent1에 메시지를 보냅니다. 파선으로 된 화살표는 에이전트 간의 데이터 흐름을 나타내고, 실선으로 된 화살표는 메시지를 쓰거나 읽는 메시지 블록에 에이전트를 연결합니다.

에이전트 라이브러리의 구성 요소

이 그림을 구현하는 코드 예제는 이 항목의 뒷부분에 나옵니다.

에이전트 프로그래밍 모델은 다른 동시성 및 동기화 메커니즘(예: 이벤트)과 비교하여 몇 가지 이점이 있습니다. 그 중 하나는 메시지 전달을 통해 개체 간에 상태 변경을 전송하여 공유 리소스에 대한 액세스를 격리시킬 수 있으므로 확장성을 향상시킬 수 있다는 점입니다. 메시지 전달은 동기화를 외부 동기화 개체 대신 데이터와 연결한다는 이점이 있습니다. 이렇게 하면 구성 요소 사이의 데이터 전송이 간단해지고 응용 프로그램에서 프로그래밍 오류를 없앨 수 있습니다.

에이전트 라이브러리를 사용하는 경우

서로 비동기적으로 통신해야 하는 작업이 여러 개 있는 경우 에이전트 라이브러리를 사용합니다. 메시지 블록 및 메시지 전달 함수를 사용하면 잠금과 같은 동기화 메커니즘이 없어도 병렬 응용 프로그램을 작성할 수 있습니다. 이렇게 하면 응용 프로그램 논리에 초점을 맞출 수 있습니다.

에이전트 프로그래밍 모델은 대개 데이터 파이프라인 또는 네트워크를 만드는 데 사용됩니다. 데이터 파이프라인은 더 큰 목표를 위해 각각 특정 작업을 수행하는 일련의 구성 요소입니다. 데이터 흐름 파이프라인의 모든 구성 요소는 다른 구성 요소로부터 메시지를 받으면 작업을 수행합니다. 해당 작업의 결과는 파이프라인 또는 네트워크의 다른 구성 요소에 전달됩니다. 이러한 구성 요소는 PPL(병렬 패턴 라이브러리)과 같은 다른 라이브러리의 좀 더 세분화된 동시성 기능을 사용할 수 있습니다.

예제

다음 예제에서는 이 항목의 앞부분에 나온 그림을 구현합니다.

// 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'.

다음 항목에서는 이 예제에 사용된 기능을 설명합니다.

관련 항목