티스토리 뷰
[Effective Modern C++] 항목 39. 단발성 사건 통신에는 void 미래 객체를 고려하라
pppgod 2020. 2. 24. 23:46두가지 스레드에서 하나의 스레드의 작업이 끝나야만 시작되는 스레드가 있다고 하자. 이 같은 상황에서 작업이 다 끝났음을 알려주는 방법은 무엇일까? 한 가지 명백한 방법은 조건 변수를 사용하는 것이다. 이 변수를 조건에 따라 저장하는 과제를 검출 과제라고 하고, 이 조건에 반응하는 과제를 반응 과제라고 하자. 어떤 방법이 있는지 알아보자.
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);
...
}
위 코드는 flag 가 true 가 될 때까지 폴링을 하게 된다. 즉, 쓸데없는 자원 낭비를 한다는 의미이다. 이를 방지하기 위해서는 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++
'C++ > Effective Modern C++' 카테고리의 다른 글
- Total
- Today
- Yesterday
- const
- MOVE
- std::forward
- async
- C
- Modern
- detach
- C++14
- auto
- 포인터
- std::move
- 다이소
- Effective Modern C++
- Join
- forward
- C++
- Override
- Future
- Perfect
- Overloading
- 보편 참조
- Effective
- Forwarding
- CPP
- Unreal
- 발아시기
- 보편참조
- 람다
- C++11
- thread
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |