방법: 실행 순서에 영향을 주는 일정 그룹 사용

동시성 런타임에서 작업이 예약되는 순서는 비결정적입니다. 그러나 일정 정책을 사용하여 작업이 실행되는 순서에 영향을 줄 수 있습니다. 이 항목에서는 동시성::SchedulingProtocol 스케줄러 정책과 함께 일정 그룹을 사용하여 작업이 실행되는 순서에 영향을 주는 방법을 보여 줍니다.

이 예제에서는 각각 다른 일정 정책을 사용하여 작업 집합을 두 번 실행합니다. 두 정책 모두 최대 처리 리소스 수를 2개로 제한합니다. 첫 번째 실행은 기본값인 정책을 사용하고 EnhanceScheduleGroupLocality 두 번째 실행은 정책을 사용합니다 EnhanceForwardProgress . 정책에서 EnhanceScheduleGroupLocality 스케줄러는 각 작업이 완료되거나 생성될 때까지 한 일정 그룹의 모든 작업을 실행합니다. 정책에서 EnhanceForwardProgress 스케줄러는 하나의 작업이 완료되거나 생성되면 라운드 로빈 방식으로 다음 일정 그룹으로 이동합니다.

각 일정 그룹에 관련 작업이 EnhanceScheduleGroupLocality 포함된 경우 일반적으로 태스크 간에 캐시 지역성이 유지되므로 성능이 향상됩니다. 이 EnhanceForwardProgress 정책을 사용하면 태스크가 진행을 진행할 수 있으며 일정 그룹 간에 공정성을 예약해야 하는 경우에 유용합니다.

예시

이 예제에서는 work_yield_agent 동시성::agent에서 파생되는 클래스를 정의합니다. 클래스는 work_yield_agent 작업 단위를 수행하고, 현재 컨텍스트를 생성한 다음, 다른 작업 단위를 수행합니다. 에이전트는 동시성::wait 함수를 사용하여 다른 컨텍스트를 실행할 수 있도록 현재 컨텍스트를 협조적으로 생성합니다.

이 예제에서는 4개의 work_yield_agent 개체를 만듭니다. 에이전트가 실행되는 순서에 영향을 주도록 스케줄러 정책을 설정하는 방법을 설명하기 위해 예제에서는 처음 두 에이전트를 하나의 일정 그룹과 연결하고 다른 두 에이전트는 다른 일정 그룹에 연결합니다. 이 예제에서는 동시성::CurrentScheduler::CreateScheduleGroup 메서드를 사용하여 동시성::ScheduleGroup 개체를 만듭니다. 이 예제에서는 서로 다른 일정 정책을 사용하여 매번 4개의 에이전트를 두 번 모두 실행합니다.

// scheduling-protocol.cpp
// compile with: /EHsc
#include <agents.h>
#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

#pragma optimize( "", off )
// Simulates work by performing a long spin loop.
void spin_loop()
{
   for (int i = 0; i < 500000000; ++i)
   {
   }
}
#pragma optimize( "", on )

// Agent that performs some work and then yields the current context.
class work_yield_agent : public agent
{
public:
   explicit work_yield_agent(
      unsigned int group_number, unsigned int task_number)
      : _group_number(group_number)
      , _task_number(task_number)
   {
   }

   explicit work_yield_agent(Scheduler& scheduler,
      unsigned int group_number, unsigned int task_number)
      : agent(scheduler)
      , _group_number(group_number)
      , _task_number(task_number)
   {
   }

   explicit work_yield_agent(ScheduleGroup& group,
      unsigned int group_number, unsigned int task_number)
      : agent(group)       
      , _group_number(group_number)
      , _task_number(task_number)
   {
   }
   
protected:
   // Performs the work of the agent.   
   void run()
   {
      wstringstream header, ss;

      // Create a string that is prepended to each message.
      header << L"group " << _group_number 
             << L",task " << _task_number << L": ";

      // Perform work.
      ss << header.str() << L"first loop..." << endl;
      wcout << ss.str();
      spin_loop();

      // Cooperatively yield the current context. 
      // The task scheduler will then run all blocked contexts.
      ss = wstringstream();
      ss << header.str() << L"waiting..." << endl;
      wcout << ss.str();
      concurrency::wait(0);

      // Perform more work.
      ss = wstringstream();
      ss << header.str() << L"second loop..." << endl;
      wcout << ss.str();
      spin_loop();

      // Print a final message and then set the agent to the 
      // finished state.
      ss = wstringstream();
      ss << header.str() << L"finished..." << endl;
      wcout << ss.str();

      done();
   }  

private:
   // The group number that the agent belongs to.
   unsigned int _group_number;
   // A task number that is associated with the agent.
   unsigned int _task_number;
};

// Creates and runs several groups of agents. Each group of agents is associated 
// with a different schedule group.
void run_agents()
{
   // The number of schedule groups to create.
   const unsigned int group_count = 2;
   // The number of agent to create per schedule group.
   const unsigned int tasks_per_group = 2;

   // A collection of schedule groups.
   vector<ScheduleGroup*> groups;
   // A collection of agents.
   vector<agent*> agents;

   // Create a series of schedule groups. 
   for (unsigned int group = 0; group < group_count; ++group)
   {
      groups.push_back(CurrentScheduler::CreateScheduleGroup());

      // For each schedule group, create a series of agents.
      for (unsigned int task = 0; task < tasks_per_group; ++task)
      {
         // Add an agent to the collection. Pass the current schedule 
         // group to the work_yield_agent constructor to schedule the agent
         // in this group.
         agents.push_back(new work_yield_agent(*groups.back(), group, task));
      }
   }

   // Start each agent.
   for_each(begin(agents), end(agents), [](agent* a) {
      a->start();
   });

   // Wait for all agents to finsih.
   agent::wait_for_all(agents.size(), &agents[0]);

   // Free the memory that was allocated for each agent.
   for_each(begin(agents), end(agents), [](agent* a) {
      delete a;
   });

   // Release each schedule group.
   for_each(begin(groups), end(groups), [](ScheduleGroup* group) {
      group->Release();
   });
}

int wmain()
{
   // Run the agents two times. Each run uses a scheduler
   // policy that limits the maximum number of processing resources to two.

   // The first run uses the EnhanceScheduleGroupLocality 
   // scheduling protocol. 
   wcout << L"Using EnhanceScheduleGroupLocality..." << endl;
   CurrentScheduler::Create(SchedulerPolicy(3, 
      MinConcurrency, 1,
      MaxConcurrency, 2,
      SchedulingProtocol, EnhanceScheduleGroupLocality));

   run_agents();
   CurrentScheduler::Detach();

   wcout << endl << endl;

   // The second run uses the EnhanceForwardProgress 
   // scheduling protocol. 
   wcout << L"Using EnhanceForwardProgress..." << endl;
   CurrentScheduler::Create(SchedulerPolicy(3, 
      MinConcurrency, 1,
      MaxConcurrency, 2,
      SchedulingProtocol, EnhanceForwardProgress));

   run_agents();
   CurrentScheduler::Detach();
}

이 예제의 결과는 다음과 같습니다.

Using EnhanceScheduleGroupLocality...
group 0,
    task 0: first loop...
group 0,
    task 1: first loop...
group 0,
    task 0: waiting...
group 1,
    task 0: first loop...
group 0,
    task 1: waiting...
group 1,
    task 1: first loop...
group 1,
    task 0: waiting...
group 0,
    task 0: second loop...
group 1,
    task 1: waiting...
group 0,
    task 1: second loop...
group 0,
    task 0: finished...
group 1,
    task 0: second loop...
group 0,
    task 1: finished...
group 1,
    task 1: second loop...
group 1,
    task 0: finished...
group 1,
    task 1: finished...

Using EnhanceForwardProgress...
group 0,
    task 0: first loop...
group 1,
    task 0: first loop...
group 0,
    task 0: waiting...
group 0,
    task 1: first loop...
group 1,
    task 0: waiting...
group 1,
    task 1: first loop...
group 0,
    task 1: waiting...
group 0,
    task 0: second loop...
group 1,
    task 1: waiting...
group 1,
    task 0: second loop...
group 0,
    task 0: finished...
group 0,
    task 1: second loop...
group 1,
    task 0: finished...
group 1,
    task 1: second loop...
group 0,
    task 1: finished...
group 1,
    task 1: finished...

두 정책 모두 동일한 이벤트 시퀀스를 생성합니다. 그러나 사용하는 EnhanceScheduleGroupLocality 정책은 두 번째 그룹의 일부인 에이전트를 시작하기 전에 첫 번째 일정 그룹의 일부인 두 에이전트를 모두 시작합니다. 사용하는 EnhanceForwardProgress 정책은 첫 번째 그룹에서 하나의 에이전트를 시작한 다음 두 번째 그룹에서 첫 번째 에이전트를 시작합니다.

코드 컴파일

예제 코드를 복사하여 Visual Studio 프로젝트에 붙여넣거나 이름이 지정된 scheduling-protocol.cpp 파일에 붙여넣은 다음 Visual Studio 명령 프롬프트 창에서 다음 명령을 실행합니다.

cl.exe /EHsc scheduling-protocol.cpp

참고 항목

일정 그룹
비동기 에이전트