🎮
MutexLock
January 05, 2024
Mutex
- atomic은 일반적인 상황에서 쓰이기 힘들다.
- 객체에 대한 원자성을 보장해야 하는 일이 훨씬 많기 때문
Mutex
문제 발생 코드
vector<int> v;
void Push()
{
for(int i = 0 ; i < 10000 ; i++)
{
v.push_back(i);
}
}
int main()
{
std::thread t1(Push);
std::thread t2(Push);
t1.join();
t2.join();
cout << v.size() << endl;
return 0;
}
실행 결과
오류 발생
- 오류 발생 이유 : vector는 크기가 2^n에 도달했을때 기존 capacity의 두배가 되는 공간을 새로 할당받고 기존 공간을 없애는데, 원자성 보장이 되지 않기 때문에, 이미 없어진 공간을 다시 없애려 하기 때문.
v.reserve(20000)
으로 미리 20000크기의 공간을 할당받으면 오류는 사라지지만, 여전히 원자성은 보장이 되지 않아 결과가 20000이 나오지 않음.
Mutex Lock
vector<int> v;
mutex m;
void Push()
{
for(int i = 0 ; i < 10000 ; i++)
{
m.lock();
v.push_back(i);
m.unlock();
}
}
int main()
{
std::thread t1(Push);
std::thread t2(Push);
t1.join();
t2.join();
cout << v.size() << endl;
return 0;
}
실행 결과
20000
- mutex lock을 통해 상호배제 달성
- 속도는 기존보다 많이 느려짐.
- mutex lock은 재귀적으로 걸 수 없음.
- m.lock(); m.lock();을 연속으로 하면 crash발생.
- 재귀적 lock을 허용하는 recursive mutex에 관해서는 추후 학습.
RAII(Resource acquisition is initialization) 패턴
vector<int> v;
mutex m;
template<typename T>
class LockGuard
{
private:
T* _mutex;
public:
LockGuard(T& m)
{
_mutex = &m;
_mutex -> lock();
}
~LockGuard()
{
-mutex -> unlock();
}
}
void Push()
{
for(int i = 0 ; i < 10000 ; i++)
{
LockGuard<std::mutex> lockGuard(m);
v.push_back(i);
if(i == 5000) break;
}
}
int main()
{
std::thread t1(Push);
std::thread t2(Push);
t1.join();
t2.join();
cout << v.size() << endl;
return 0;
}
- 래퍼 클래스를 통해 레이어(레벨) 종료시 인스턴스가 스택에서 해제되는 원리를 이용하여
unlock()
을 따로 명시하지 않고도 소멸자를 통해 자동으로unlock()
가능. lock_guard
,unique_lock
등의 표준이 있음`lock_guard
는 위의 예시와 동일하게 사용unique_lock
은 추가 기능이 더 있음.unique_lock<mutex> uniqueLock(m, defer_lock)
과 같이defer_lock
옵션을 주면 명시적으로 lock을 하는 타이밍을 정할 수 있음.uniqueLock.lock()
과 같이 사용.