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;
}