C++11 상호 배제(Mutex)
19 Aug 2017 | C++ ThreadMutex 종류
C++ 표준에서는 Mutex 종류는 크게 2가지가 있습니다. 하나는 일반 Mutex이며, 다른 하나는 타임 아웃 Mutex 입니다.
일반 Mutex
일반 Mutex에는 std::mutex
, std::recursive_mutext
두 종류가 있습니다. 두 클래스는
공통으로 다음과 같은 함수를 갖고 있습니다.
- lock() : Thread에서 락 점유를 시도하며, 락을 획득할 때까지 무한정 대기함.
- try_lock() : 락 점유를 시도하며, 성공시
true
, 실패시false
를 바로 리턴함. - unlock() : 락을 해제함.
std::mutex
는 중복해서 lock()
을 호출할 경우 ‘데드락(Deadlock)’ 상황에 빠질 수 있습니다.
std::recursive_mutex
는 lock()
을 여러 번 호출해도 상관없지만, 대신 락을 해제할 때는
동일한 횟수만큼 unlock()
을 호출해주어야 락이 해제됩니다.
타임 아웃 Mutex
타임 아웃 Mutex에는 std::timed_mutex
, std::recursive_timed_mutex
가 있습니다.
그리고 다음과 같은 함수들을 갖고 있습니다.
- lock() : Thread에서 락 점유를 시도하며, 락을 획득할 때까지 무한정 대기함.
- try_lock() : 락 점유를 시도하며, 성공시
true
, 실패시false
를 바로 리턴함. - unlock() : 락을 해제함.
- try_lock_for(rel_time) : 주어진 시간까지 락 점유를 시도. 시간은 상대 시간임. 성공시
true
, 실패시false
를 바로 리턴. - try_lock_until(abs_time) : 주어진 시간까지 락 점유를 시도. 시간을 절대 시간임. 성공시
true
, 실패시false
를 바로 리턴.
lock 클래스
Mutex를 좀 더 사용하기 쉽게 Wrapping한 클래스입니다. lock
클래스의 소멸자는 자동으로
내부의 Mutex를 해제해주기 때문에, 각 함수의 로컬 변수로 사용할 경우 함수를 빠져나오면서 자연스럽게
락이 해제됩니다. 즉, 락 해제에 신경을 덜 쓸 수 있습니다.
lock_guard
는 생성자에서 락 점유를 시도하며, 점유를 성공할 때까지 Blocking 됩니다.
unique_lock
은 객체를 미리 선언해두고, 나중에 필요한 시점에 락 점유를 시도할 수 있습니다. lock()
, try_lock()
, try_lock_for()
, try_lock_until()
등의 함수도 제공합니다.
lock 예제
#include <mutex> using namespace std; class LockExample { public: void sendMessage(int what, int arg1, int arg2); private: mutable mutex mMutex; }; void LockExample::sendMessage(int what, int arg1, int arg2) { unique_lock<mutex> lock<mMutex> mMessageSender->sendMessage(what, arg1, arg2); }
이중 검사 락 알고리즘
멀티 쓰레드 환경에서 어떤 부분이 단 한 번만 호출이 되어야 한다면 이중 검사 락 알고리즘을
사용할 수 있습니다. 물론, call_once()
를 사용한다면 굳이 이 알고리즘을 사용할 필요는 없을 것 같습니다.
아래 예제는 isInitialized
변수를 확인한 다음 초기화를 수행하고 isInitialized
변수의 값을 true
로
세팅하는 예제입니다. isInitialized
변수가 true
로 세팅되기 직전에 다른 쓰레드가 끼어드는 것을
방지하기 위해서 이중 검사 락 알고리즘을 사용했습니다.
#include <cstdio> #include <thread> #include <mutex> #include <vector> using namespace std; class LockExample { public: void init() { printf("Init()\n"); } }; bool isInitialized = false; mutex mtx; LockExample exam; void func() { if (isInitialized == false) { unique_lock<mutex> lock(mtx); if (isInitialized == false) { exam.init(); isInitialized = true; } } printf("func()\n"); } int main() { vector<thread> threads; for (int i = 0; i < 5; i++) { threads.push_back(thread{func}); } for (auto &t : threads) { t.join(); } return 0; }