lock_guard deposit: 100
unique_lock owns before lock: false
unique_lock owns after lock: true
unique_lock owns after unlock: false
audit: withdraw finished
withdraw result: true
final balance: 60
deposit_with_lock_guard()에서는 lock object가 만들어지는 순간 mutex를 잡고, 함수가 끝나면 자동으로 unlock됩니다.
withdraw_with_unique_lock()에서는 std::defer_lock으로 lock을 미루고, lock.lock() 시점에 ownership을 얻습니다.
balance_를 수정한 뒤 lock.unlock()을 호출하므로 audit()은 mutex를 잡지 않은 상태에서 실행됩니다.
5. 실행 결과에서 봐야 할 것
출력에서 가장 중요한 부분은 owns_lock() 값의 변화입니다.
std::unique_lock은 현재 자신이 mutex ownership을 갖고 있는지 상태로 추적합니다.
상태 변화
std::unique_lock 생성
std::defer_lock 때문에 아직 mutex를 잡지 않음
owns_lock() == false
lock.lock() 호출
mutex ownership 획득
owns_lock() == true
balance_ 수정
lock.unlock() 호출
mutex ownership 해제
owns_lock() == false
audit() 실행
mutex를 잡지 않은 상태에서 오래 걸릴 수 있는 작업 수행
std::lock_guard에는 이런 상태 변화 API가 없습니다.
그 제한이 단점만은 아닙니다. 오히려 단순한 critical section에서는 lock 범위를 바꿀 수 없다는 점이 코드의 의도를 더 분명하게 만듭니다.
6. 그림으로 이해하기
std::lock_guard는 scope 시작부터 끝까지 단순하게 mutex를 잡고, std::unique_lock은 필요할 때 lock ownership 상태를 바꿀 수 있습니다.
그림에서 봐야 할 차이는 unlock 시점입니다.
std::lock_guard는 scope가 끝날 때 unlock되는 구조라 중간에 critical section을 줄일 수 없습니다.
std::unique_lock은 필요한 작업만 mutex 안에서 수행하고, audit이나 I/O처럼 오래 걸릴 수 있는 작업은 unlock 이후로 뺄 수 있습니다.
7. 자주 하는 오해
std::unique_lock이 항상 더 좋은 선택이다. 더 유연한 도구일 뿐입니다. 단순한 scope lock에는 std::lock_guard가 더 읽기 쉽고 의도가 분명합니다.
std::lock_guard는 예외가 나면 unlock하지 못한다. std::lock_guard는 RAII object입니다. stack unwinding 중 destructor가 호출되면 mutex를 unlock합니다.
std::unique_lock을 쓰면 lock 범위가 자동으로 최적화된다. unlock 시점은 개발자가 직접 설계해야 합니다. unique_lock은 도구일 뿐 critical section을 줄여주지는 않습니다.
owns_lock()이 true이면 공유 데이터가 항상 안전하다. 현재 lock object가 mutex를 소유한다는 뜻입니다. 보호해야 할 데이터와 mutex의 대응 관계가 코드에서 일관되어야 안전합니다.
mutex를 오래 잡아도 correctness만 맞으면 괜찮다. correctness는 필요조건입니다. lock을 오래 잡으면 contention이 늘고 tail latency가 나빠질 수 있습니다.
lock 관련 코드는 “동작한다”보다 “어디까지가 critical section인지 분명한가”를 기준으로 읽어야 합니다.
8. 실무에서는 어떻게 볼까?
실무 코드 리뷰에서는 먼저 lock 범위를 봅니다.
공유 데이터 접근만 critical section 안에 있고, logging, callback, file I/O, network I/O 같은 작업이 lock 밖에 있는지 확인해야 합니다.
단순히 “thread-safe하다”는 말만으로는 부족합니다.
코드 리뷰 질문
확인할 내용
권장 방향
lock 범위가 짧은가?
공유 데이터 접근 외 작업이 critical section에 들어갔는지 확인
필요하면 std::unique_lock으로 중간 unlock
unlock 누락 가능성이 있는가?
직접 mutex.lock(), mutex.unlock()을 호출하는지 확인
RAII lock object 사용
condition_variable을 쓰는가?
wait 중 lock release/reacquire가 필요한지 확인
std::unique_lock 사용
lock object를 함수 밖으로 넘기는가?
ownership 이동이 필요한지 확인
std::unique_lock을 move-only object로 다룸
성능 문제가 있는가?
lock 횟수보다 lock을 잡는 시간과 contention을 먼저 측정
critical section 축소, shard, lock-free 구조 검토
기본 원칙은 단순합니다.
lock을 잡고 바로 공유 데이터를 수정한 뒤 scope가 끝나면 std::lock_guard가 적합합니다.
lock을 늦게 잡거나, 중간에 풀거나, condition_variable과 함께 써야 한다면 std::unique_lock이 적합합니다.
9. 정리
std::lock_guard와 std::unique_lock은 모두 mutex unlock을 RAII로 보장하는 도구입니다.
std::lock_guard는 짧고 단순한 critical section에 가장 잘 맞습니다.
std::unique_lock은 lock ownership을 상태로 관리해야 할 때 사용합니다.
condition_variable, defer_lock, try_lock, 중간 unlock이 필요하면 std::unique_lock을 검토합니다.
실무에서는 lock 타입보다 critical section 범위와 unlock 시점을 명확히 하는 것이 더 중요합니다.