[C++] Q: std::condition_variable은 왜 std::unique_lock만 쓰나요?

https://stackoverflow.com/questions/13099660/c11-why-does-stdcondition-variable-use-stdunique-lock

Q: std::condition_variable를 사용할 때 인자로 std::unique_lock만 쓰던데, 이걸 왜 하필 이것만 쓰는지 모르겠네요.

제가 이 문서를 이해한 바에 따르면, std::unique_lock은 기본적으로 lock guard입니다. 두 lock 사이의 상태를 치환하는게 가능하죠.

저는 이제껏 이런데다 pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)라는 함수를 사용했는데요. 아마 STL을 POSIX 규격으로 사용하는 것인듯합니다.
근데 이건 mutex를 인자로 받지, lock을 받는건 아니죠.

여기에 무슨 차이가 있는걸까요?
std::condition_variable는 std::unique_lock에만 최적화되어있는건가요??
만약 그렇다면, 정확히 얼마나 더 빠를까요?



A: 기술적인 이유는 없을까요?
전 cmeerw의 답을 추천합니다. 기술적인 이유까지 잘 제시했다고 보기 때문이죠.
한번 달려봅시다.

표준 위원회가 condition_variable에 mutex를 넣기로 결정했다고 칩시다.
그럼 코드는 이러한 형태가 될겁니다.
**void foo() **
{ **
**    mut.lock(); **
**    //mut가 이 스레드에서 lock을 겁니다.

**    ****while (not_ready) **
**        cv.wait(mut); **
**    **//mut가 이 스레드에서 lock을 겁니다.
**    mut.unlock(); **
}

이건 condition_variable를 사용하지 않은 방법이죠.
**/****/mut가 이 스레드에서 lock을 겁니다.로 표시된 **구역에서는 예외 안전성에 대한 문제가 있습니다.

그것도 꽤 심각하죠.
만약 저 공간들이나 cv.wait에서 예외가 던져진다면, try/catch가 예외를 잡아서 잠금을 해제하지 않는 한 mutex의 잠금 상태가 누수(leak)됩니다. 잠금이 풀리지 않아요.
그런데도 이건 프로그래머들에게 요청이 많이 되는 코드 형태입니다.

프로그래머가 예외 안전한 코드를 쓸 줄 알고, unique_lock도 쓸 줄 안다고 칩시다.
이제 코드는 다음과 같이 변합니다.

**void foo() **
{ **
**    unique_lock lk(mut); **
**    ***//mut가 이 스레드에서 lock을 겁니다.
**    while (not_ready) **
**        cv.wait(
lk.mutex
()); **
**    ****//**mut가 이 스레드에서 lock을 겁니다.
}

아까보단 낫긴 하지만, 여전히 최고의 상황은 아닙니다.
저 condition_variable의 인터페이스는 프로그래머가 작업을 함에 있어 많은 노력을 필요로 합니다.
->condition_variable interface is making the programmer go out of his way to get things to work.
실수로 lk가 mutex를 참조하지 못한다면, 널포인터 역참조가 발생할 수 있습니다.

그리고 condition_variable::wait는 해당 스레드가 mut에 대한 lock을 소유하고 있는지 확인할 방법이 없습니다.

기억해두세요. 프로그래머는 mutex를 노출시키는 잘못된 unique_lock의 멤버함수를 선택할 수도 있습니다.
*lk.release()가 그런 예죠.

그럼 이제 unique_lock를 받아먹는 실제 condition_variable API로 코드를 작성해봅시다.

**void foo() **
**{ **
**    unique_lock lk(mut); **
**    ****//**mut가 이 스레드에서 lock을 겁니다.
**    while (not_ready) **
**        cv.wait(lk); **
**    ****//**mut가 이 스레드에서 lock을 겁니다.
}

이 코드는 아주 간단합니다.
예외에 안전하기도 하죠

저기서 wait 함수는  lk.owns_lock()을 체크하고 false라면 예외를 던집니다.

이게 condition_variable의 API 디자인을 유도한 기술적인 이유입니다.

게다가 condition_variable은 lock_guard도 인자로 받지 않습니다. 왜일까요? lock_guard은 파괴될때까지 해당 mutex의 잠금을 소유하잖아요.

하지만 condition_variable::wait가 호출될때, mutex의 lock을 묵시적으로 해제(release)되게 됩니다.
그래서 저 동작은 lock_guard의 사용사례(use case)와 구문(statement)가 부합되지 않습니다.
->So that action is inconsistent with the lock_guard use case / statement.

lock을 함수에서 반환하거나, 컨테이너에 집어넣고서 예외 안전한 방법으로 스코프 기반 패턴이 아닌 mutex들의 잠금을 걸거나 해제하기 위해서는 unique_lock가 필요했습니다. 때문에 unique_lock은 condition_variable::wait에 가장 자연스런 방법입니다.