이 글은 MSDN 글, "Solving The Dining Philosophers Problem With Asynchronous Agents"를 참고하여 작성되었습니다.

Asynchronous Agents Library로 Dining Philosophers 문제 해결하기 - 1
Asynchronous Agents Library로 Dining Philosophers 문제 해결하기 - 2

오래 기다리셨습니다; 그간 일이 바빠서;; 어쨌든 지난번에 Concurrecy::agent 에서 상속받은 Philosopher 클래스를 살펴봤었죠. 아래 두 함수만 제외하고 말입니다.

자 먼저 젓가락을 집는 함수입니다. 젓가락 한쌍을 동시에 집어야지 하나만이라도 먼저 집으려고 하다간 서로 젓가락 하나씩 잡고 기다리는 데드락 상황이 발생할 수 있습니다. 이를 위해 쓰이는 것이 지난 회에 잠깐 언급했든 join 메시지 블록입니다. 그 중에서도 non_greedy 버전을 사용해야 합니다. non_greedy 버전은 명시된 타겟을 모두 얻을 수 있을 때에만 실제 획득을 시도합니다. gready 버전을 사용하면 전술한 것처럼 데드락이 발생할 수 있습니다.

   73     std::vector<Chopstick*> PickupChopsticks()

   74     {

   75         //join 생성

   76         Concurrency::join<Chopstick*,Concurrency::non_greedy> j(2);

   77         m_LeftChopstickProvider->link_target(&j);

   78         m_RightChopstickProvider->link_target(&j);

   79 

   80         //젓가락 한쌍을 집습니다.

   81         return Concurrency::receive (j);

   82     } 


젓가락을 내려놓은 것은 간단합니다. 비동기 메시지 전송 함수인 Concurrency::asend()를 사용하여 젓가락이 이용가능함을 알리면 끝입니다.

   83     void PutDownChopsticks(std::vector<Chopstick*>& v)

   84     {

   85         Concurrency::asend(m_LeftChopstickProvider,v[0]);

   86         Concurrency::asend(m_RightChopstickProvider,v[1]);

   87     }


마지막으로 철학자들과 젓가락, 젓가락제공자를 가지고 이들 모두를 셋업하는 역할을 하는 Table 클래스입니다. 주석을 참고하시면 쉽게 이해하실 수 있을 겁니다.

  100 template<class PhilosopherList>

  101 class Table

  102 {

  103     PhilosopherList & m_Philosophers;

  104     std::vector<ChopstickProvider*> m_ChopstickProviders;

  105     std::vector<Chopstick*> m_Chopsticks;

  106 

  107     //이 생성자는 Table 클래서에서 유일한 public 메소드로 vector 변수들을 초기화하고 각 철학자에게 젓가락제공자를 할당합니다:

  108 public:

  109     Table(PhilosopherList& philosophers): m_Philosophers(philosophers)

  110     {

  111         //젓가락 및 젓가락제공자 vector를 채웁니다

  112         for(size_t i = 0; i < m_Philosophers.size();++i)

  113         {

  114             m_ChopstickProviders.push_back(new ChopstickProvider());

  115             m_Chopsticks.push_back(new Chopstick("chopstick"));

  116             //젓가락제공자에 젓가락을 놓습니다

  117             send(m_ChopstickProviders[i],m_Chopsticks[i]);

  118         }

  119         //철학자들을 식탁 자리에 앉힙니다

  120         for(size_t leftIndex = 0; leftIndex < m_Philosophers.size();++leftIndex)

  121         {

  122             //rightIndex 계산

  123             size_t rightIndex = (leftIndex+1)% m_Philosophers.size();

  124 

  125             //왼쪽,오른쪽 제공자를 해당 철학자에 부여합니다

  126             Concurrency::asend(& m_Philosophers[leftIndex].LeftChopstickProviderBuffer,

  127                 m_ChopstickProviders[leftIndex]);

  128             Concurrency::asend(& m_Philosophers[leftIndex].RightChopstickProviderBuffer,

  129                 m_ChopstickProviders[rightIndex]);

  130         }

  131     }

  132     ~Table(){

  133         m_ChopstickProviders.clear();

  134         m_Chopsticks.clear();

  135     }

  136 

  137 };


드디어 대망의 main() 함수입니다. 상태표시를 위한 call 블록과 C++0x 람다의 사용 이외에는, 전술할 클래스들을 사용하고 있을 뿐입니다.

  206 int main()

  207 {

  208     //tr1 array를 사용해 철학자들을 생성합니다

  209     std::tr1::array<Philosopher,5> philosophers = {"Socrates", "Descartes", "Nietzche", "Sartre", "Amdahl"};

  210     Table<std::tr1::array<Philosopher,5>> Table(philosophers);

  211     //상태표시에 이용할 call 블록들의 목록을 생성합니다

  212     std::vector<Concurrency::call<PhilosopherState>*> displays;

  213     //철학자 에이전트를 구동하고 상태표시 항목을 생성합니다

  214     std::for_each(philosophers.begin(),philosophers.end(),[&displays](Philosopher& cur)

  215     {

  216         //상태표시용 call 블록을 하나 만듭니다

  217         Concurrency::call<PhilosopherState>* consoleDisplayBlock = new Concurrency::call<PhilosopherState>([&](PhilosopherState in){

  218             //cout은 각 출력 사이의 스레드안정성을 보장하지 않습니다

  219             if(in == Eating)

  220                 std::cout << cur.m_Name << " is eating\n";

  221             else

  222                 std::cout << cur.m_Name << " is  thinking\n";

  223         });

  224         //상태표시 블록을 연결하고 벡터에 저장해둡니다

  225         cur.CurrentState.link_target(consoleDisplayBlock);

  226         displays.push_back(consoleDisplayBlock);

  227         //그리고 에이전트를 구동합니다

  228         cur.start();

  229     });

  230     //모두 완료되기를 대기

  231     std::for_each(philosophers.begin(),philosophers.end(),[](Philosopher& cur)

  232     {

  233         cur.wait(&cur);

  234     });

  235 

  236     displays.clear();

  237 };


이상을 실행하면 다음과 유사한 결과를 확인하실 수 있습니다.


주석에도 나와있듯이 스레드에 안전하지 않은 cout 출력으로 가끔 상태 메시지가 섞여였음을 확인할 수 있습니다. 그것 이외에는 철학자들이 사이좋게 식사를 하고 있음을 알 수 있습니다.

이렇듯 AAL을 사용하면 저수준의 스레드 함수나 동기화 개체들을 직접 다루지 않고도 쉽게 병렬 수행 작업을 작성할 수 있습니다. 병렬화에 고민하지 않고, 해당 응용프로그램의 도메인 문제에만 집중할 수 있는 것이죠.


이상입니다. 이제 새로운 로고와 함께 VS2010의 베타2도 나왔으니, 새로운 주제로 다시 찾아뵙지요. ^^