NeuroWhAI의 잡블로그

[C++] lock tag(defer_lock, try_to_lock, adopt_lock) 설명 + scoped_lock 본문

개발 및 공부/언어

[C++] lock tag(defer_lock, try_to_lock, adopt_lock) 설명 + scoped_lock

NeuroWhAI 2018. 8. 26. 16:44



mutex 헤더에 정의되어 있는 이 3개의 lock tag들은
mutex를 받는 표준 라이브러리 함수나 객체가 해당 mutex를 어떻게 취급할지 지정할 수 있게 해줍니다.

defer_lock : 뮤텍스를 잠그지 않습니다.
try_to_lock : 뮤텍스를 잠그려고 시도합니다. (이미 잠겨있다면 대기하지 않고 실패합니다)
adopt_lock : 이미 뮤텍스가 현재 스레드에 의해 잠겨있다고 가정합니다. (=잠그지 않습니다)

이걸 어디다 쓰느냐... 하실 수 있지만 생각해봅시다.
두 뮤텍스 a, b를 한번에 잠궈야할 일이 생긴다고 합시다.
이때 우리는 std::lock 함수를 사용해서 데드락 없이 둘 이상의 뮤텍스를 잠글 수 있으므로 아래와 같이 할 수 있을겁니다.
std::lock(a, b);
음... 잠그는건 성공했네요.
그럼 필요한 작업이 끝났으면 해제도 해야겠죠?
a.unlock();
b.unlock();
이렇게 하면 될까요?
물론 되기야 하겠지만 이 방식은 행여나 이전 작업 코드에서의 예외로 인해 unlock이 수행되지 않을 수 있는 상황이 발생할 수 있습니다.
그래서 우리가 unique_lock이나 lock_guard를 사용하는거죠?
아래와 같이 바꿔볼 수 있습니다.
std::lock(a, b);
std::lock_guard<std::mutex> lk1(a, std::adopt_lock);
std::lock_guard<std::mutex> lk2(b, std::adopt_lock);
자 adopt_lock이 쓰였네요.
adopt_lock은 이미 뮤텍스가 잠겨있다고 가정한다고 했습니다.
실제로 lock_guard를 생성하기 전 std::lock으로 잠군 상태입니다.
이치가 맞죠?
아래처럼 순서를 바꿔서도 할 수 있습니다.
std::lock_guard<std::mutex> lk1(a, std::defer_lock);
std::lock_guard<std::mutex> lk2(b, std::defer_lock);
std::lock(a, b);
defer_lock을 사용해서 뮤텍스를 바로 잠그지 않고 lock_guard를 생성한 뒤 lock 함수로 잠궜습니다.
상황에 따라 적절한 lock tag들을 사용하면 되겠습니다.

사실 C++17부터 지원하는 scoped_lock이란 녀석을 이용하면 위 3줄을 1줄로 줄일 수 있습니다.
std::scoped_lock lk(a, b);
위 과정을 자동으로 수행해주는 녀석이라고 보시면 됩니다.
scoped_lock도 lock tag를 지정할 수 있는데 adopt_lock만 지원한다는 것 참고하시면 되겠습니다.

그럼 즐프밍!




Comments