티스토리 뷰
[Effective Modern C++] 항목 37. std::thread들을 모든 경로에서 합류 불가능하게 만들어라
pppgod 2020. 2. 9. 19:07모든 스레드 객체는 합류 가능이거나 합류 불가능이다. 여기서 말하는 합류는 join 을 사용할 수 있는 상태를 말한다. 합류 불가능 스레드는 다음의 목록이 존재한다.
- 기본 생성된 std::thread
실행할 함수가 없이 생성된 객체는 바탕 실행 스레드와는 대응되지 않는다.
- 다른 std::thread 객체로 이동된 후의 std::thread 객체
이동이 완료되면 기존의 스레드는 대응되는 스레드가 존재하지 않는다.
- join 에 의해 합류된 std::thread
join 이 완료되면 대응되는 스레드가 존재하지 않는다.
- detach 에 의해 탈착된 std::thread
스레드 객체와 바탕 스레드와의 연결이 끊어져 합류 불가능 상태가 된다.
문제점
합류 가능성이 중요한 이유는 합류 가능 스레드의 소멸자가 호출되면 프로그램이 종료되기 때문이다. 예제를 통해 살펴보자.
constexpr auto tenMillion = 10'000'000;
bool doWork(std::function<bool(int)> filter,
int maxVal = tenMillion)
{
std::vector<int> goodVals;
std::thread t([&filter, maxVal, &goodVals]
{
for (auto i = 0; i <= maxVal; ++i)
{ if (filter(i)) goodVals.push_back(i); }
});
auto nh = t.native_handle();
...
if (conditionsAreSatisfied()) {
t.join();
performComputation(goodVals);
return true;
}
return false;
}
위의 코드에서 conditionsAreSatisfied() 함수가 true 를 반환하면 문제는 발생하지 않는다. 그러나 false 를 반환하는 경우에는 t 의 소멸자가 호출되어 프로그램이 종료된다. 왜 다른 방식으로 동작하지 않고 프로그램이 종료될까? 만약 프로그램이 종료되지 않는 경우를 살펴보자.
암묵적 join
스레드의 소멸자가 스레드가 완료되기를 기다리는 방식이다. 합리적으로 보이지만 사실은 그렇지 않다. conditionsAreSatisfied() 가 이미 false 를 반환해서 함수가 종료되어도 마땅한 상황임에도 스레드가 종료되길 기다려야 하는 것은 합리적이지 않다.
암묵적 detach
스레드가 실행중인 상태에서 false 를 반환하여 doWork 함수가 종료되었다고 가정해보자. doWork 가 종료되며 지역변수들도 함께 소멸된다. 문제는 위의 코드에서 스레드는 goodVals 라는 지역변수를 참조로 사용하고 있다. 그렇게되면 스레드는 소멸된 변수에 접근하게 되어 미정의 행동을 하게 되는데, 이를 디버깅하기란 매우 어려울 것이다.
결과적으로 두가지 경우 모두 적절하지 않으므로 프로그램을 종료하기로 결정되었다. 다행히도 우리는 RAII 방식을 사용하여 문제를 해결할 수 있다.
class ThreadRAII {
public:
enum class DtorAction { join, detach };
ThreadRAII(std::thread&& t, DtorAction a)
: action(a), t(std::move(t)) {}
~ThreadRAII()
{
if (t.joinable()) {
if (action == DtorAction::join) {
t.join();
} else {
t.detach();
}
}
}
ThreadRAII(ThreadRAII&&) = default;
ThreadRAII& operator=(ThreadRAII&&) = default;
std::Thread& get() { return t; }
private:
DtorAction action;
std::thread t;
};
참고 서적
스콧 마이어스, Effective Modern C++
'C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 항목 40. 동시성에는 std::atomic을 사용하고, volatile은 특별한 메모리에 사용하라 (0) | 2020.02.25 |
---|---|
[Effective Modern C++] 항목 39. 단발성 사건 통신에는 void 미래 객체를 고려하라 (0) | 2020.02.24 |
[Effective Modern C++] 항목 36. 비동기성이 필수일 때에는 std::laynch::async를 지정하라 (0) | 2020.02.02 |
[Effective Modern C++] 항목 35. 스레드 기반 프로그래밍보다 과제 기반 프로그래밍을 선호하라 (0) | 2020.02.02 |
[Effective Modern C++] 항목 34. std::bind보다 람다를 선호하라 (0) | 2020.01.18 |
- Total
- Today
- Yesterday
- Effective
- thread
- const
- auto
- forward
- C++
- C++11
- 발아시기
- MOVE
- Join
- 포인터
- Overloading
- 보편 참조
- 보편참조
- C
- Unreal
- std::move
- Override
- 다이소
- Forwarding
- Modern
- Perfect
- std::forward
- CPP
- C++14
- async
- 람다
- Future
- Effective Modern C++
- detach
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |