티스토리 뷰

 두가지 스레드에서 하나의 스레드의 작업이 끝나야만 시작되는 스레드가 있다고 하자. 이 같은 상황에서 작업이 다 끝났음을 알려주는 방법은 무엇일까? 한 가지 명백한 방법은 조건 변수를 사용하는 것이다. 이 변수를 조건에 따라 저장하는 과제를 검출 과제라고 하고, 이 조건에 반응하는 과제를 반응 과제라고 하자. 어떤 방법이 있는지 알아보자.

std::condition_variable

 std 에서 제공하는 방법으로 다음과 같이 사용할 수 있다.

std::condition_variable cv;
std::mutex m;

// 검출 과제
{
  ...
  cv. notify_one();
}

// 반응 과제
{
  ...
  {
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk);
    ...
  }
  ...
}

 위 코드는 잘 작동하는 것처럼 보인다. 그러나 몇몇 아쉬운 점이 있다. 바로 뮤텍스를 사용한다는 점이다. 검출 과제반응 과제가 동시에 같은 변수에 접근하는 일은 일어나지 않는다. 따라서 뮤텍스를 사용할 필요가 전혀 없다. 그런데 이보다 더 큰 문제가 있다. 만일 반응 과제가 wait 을 하기 전에 notify 를 하게 되면 반응 과제는 무한히 기다리게 된다. 또한 만일 가짜 기상이 일어나는 경우에 wait 이 풀려버리게 된다.

bool 플래그

 코드를 먼저 살펴보자.

std::atomic<bool> flag(false);

// 검출 과제
{
  ...
  flag = true;
}

// 반응 과제
{
  ...
  while (!flag);
  ...
}

 위 코드는 flagtrue 가 될 때까지 폴링을 하게 된다. 즉, 쓸데없는 자원 낭비를 한다는 의미이다. 이를 방지하기 위해서는 wait 을 사용하는 수 밖에 없다. 바로 이전에 사용했던 std::condition_variable 과 함께 사용하는 방법이다.

std::condition_variable cv;
std::mutex m;

bool flag(false);

// 검출 과제
{
  ...
  {
    std::lock_guard<std::mutex> g(m);
    flag = true;
  }
}

// 반응 과제
{
  ...
  {
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk, [] { return flag; });
    ...
  }
  ...
}

 위와 같은 방법으로는 지속적인 폴링도 일어나지 않으며, 가짜 기상이 발생하더라도 wait 상태를 유지한다. 그러나 mutex 를 사용해야 한다는 점과 bool 변수까지 써야한다는 점은 무언가 깔끔하지 않다. 또한 반응 과제에서 wait 이 먼저 호출되어야 한다는 점은 변하지 않았다.

std::promise

 앞선 항목에서 살펴보았던 문제를 모두 해결하는 방법이다. 코드는 아주 간단하다.

std::promise<void> p;

// 검출 과제
{
  ...
  p.set_value();
}

// 반응 과제
{
  ...
  p.get_future().wait();
  ...
}

 한가지 주의해야할 점은 힙 메모리 관리를 해야한다는 점과 공유 상태가 존재한다는 점이다. 더 중요한 것은 std::promised 를 한 번만 설정할 수 있다는 점이다. 즉 단발성이라는 의미이다. 이러한 특징을 생각하면 void 미래 객체를 사용하는 것이 합리적이다.

std::promise<void> p;

// 반응 과제
void react();

// 검출 과제
void detect()
{
  ThreadRAII tr(
    std::thread([]
                {
                  p.get_future().wait();
                  react();
                }),
    ThreadRAII::DtorAction::join
  );
  ...
  p.set_value();
  ...
}

더 나아가 반응 과제가 여러 개이어도 문제가 없다.

std::promise<void> p;

void detect()
{
  auto sf = p.get_furue().share();
  
  std::vector<std:;thread> vt;
  
  for (int i = 0; i < threadsToRun; ++i) {
    vt.emplace_back([sf]{ sf.wait();
                          react(); });
  }
  
  ...
  p.set_value();
  ...
  
  for (auto& t : vt) {
    t.join();
  }
}

 

참고 서적

스콧 마이어스, Effective Modern C++
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함