티스토리 뷰

mutex 기반으로 작성하고, condition variable로 단점을 보완해 본다

mutex 기반


  
#include <mutex>
template<typename T>
class LockStack
{
public:
LockStack() {}
LockStack(const LockStack&) = delete;
LockStack& operator=(const LockStack&) = delete;
private:
stack<T> _stack;
mutex _mutex;
};

push 연산


  
void Push(T value)
{
lock_guard<mutex> lock(_mutex);
_stack.push(std::move(value));
}
  • lock_guard 이용
  • cf) 이동이 가능한 것들은, move를 이용해서 빠른 연산 지원

pop 연산

  • 고전적으로는 empty() 연산으로 체크 후, top, pop 실행: empty → top → pop
  • 하지만, 멀티 스레드 환경이라면 데이터 불일치 상황이 가능함 (다른 스레드가 중간에 연산하는 경우가 가능)

  
bool TryPop(&outValue)
{
lock_guard<mutex> lock(_mutex);
if (_stack.empty())
{
return false;
}
outValue = std::move(_stack.top());
_stack.pop();
return true;
}
  • mutex로 락을 걸어두고, empty → top → pop
  • 지금 구현에서는 한 번에 꺼내는 연산 지원
    • cf) top(), pop() 나눠서 하는 이유 : 한 번에 꺼낸다면, 크래쉬 발생 가능성이 존재
      • 메모리가 부족해서 복사과정에서 익셉션이 발생한다면, 자료구조가 망가짐
      • 따라서, 두 단계로 나눔
      • 다만, 그런 크래쉬라면 뻗게 두는 게 나을 것임 (익셉션처리가 아닌)

전체 코드


  
#include <mutex>
template<typename T>
class LockStack
{
public:
LockStack() {}
LockStack(const LockStack&) = delete;
LockStack& operator=(const LockStack&) = delete;
void Push(T value)
{
lock_guard<mutex> lock(_mutex);
_stack.push(std::move(value));
}
bool TryPop(&outValue)
{
lock_guard<mutex> lock(_mutex);
if (_stack.empty())
{
return false;
}
outValue = std::move(_stack.top());
_stack.pop();
return true;
}
private:
stack<T> _stack;
mutex _mutex;
};

아쉬운 점 (busy waiting)

데이터가 없더라도 데이터가 있는지 없는지를 체크할 수 없으니까, TryPop을 무한루프로 처리해야 한다. TryPop의 반환값이 false 임을 통해서 데이터가 없음을 확인하게 되는 것이다.

 

🔥 데이터가 채워지면 pop 할 수 있도록 하자

condition variable 기반


  
#include <mutex>
template<typename T>
class LockStack
{
public:
LockStack() {}
LockStack(const LockStack&) = delete;
LockStack& operator=(const LockStack&) = delete;
private:
stack<T> _stack;
mutex _mutex;
condition_variable _condVar;
};
  • 멤버변수로 condition_variable을 추가

push 연산


  
void Push(T value)
{
lock_guard<mutex> lock(_mutex);
_stack.push(std::move(value));
// TODO: 여기서 락 해제하는것이 좋을 것
_condVar.notify_one(); // 대기하고있는 하나를 깨움
}

pop 연산


  
void WaitPop(T& outValue)
{
unique_lock<mutex> lock(_mutex); // condition variable 은 내부적으로 락을 풀었다가 막(?) 하기 때문에, lock_guard가 아닌 unique_lock 사용.
_condVar.wait(lock, [this] { return _stack.empty() == false; }); // 1. 인수로 unique_lock을 받음을 확인할 수 있음 2. predicate로 조건(데이터가 존재할때까지)
// 락을 잡아서 들어갔다가, 조건을 체크해서
// 만약 조건을 만족하지 않으면
// 락을 풀어주고, 잠든다.
// push연산 수행되면 (시그널(notify_one)의 발생), 일어나서 락을 잡고 다시 실행됨
outValue = std::move(_stack.top());
_stack.pop();
}

 

전체코드


  
#include <mutex>
template<typename T>
class LockStack
{
public:
LockStack() {}
LockStack(const LockStack&) = delete;
LockStack& operator=(const LockStack&) = delete;
void Push(T value)
{
lock_guard<mutex> lock(_mutex);
_stack.push(std::move(value));
// TODO: 여기서 락 해제하는것이 좋을 것
_condVar.notify_one(); // 대기하고있는 하나를 깨움
}
bool TryPop(&outValue)
{
lock_guard<mutex> lock(_mutex);
if (_stack.empty())
{
return false;
}
outValue = std::move(_stack.top());
_stack.pop();
return true;
}
void WaitPop(T& outValue)
{
unique_lock<mutex> lock(_mutex); // condition variable 은 내부적으로 락을 풀었다가 막(?) 하기 때문에, lock_guard가 아닌 unique_lock 사용.
_condVar.wait(lock, [this] { return _stack.empty() == false; }); // 1. 인수로 unique_lock을 받음을 확인할 수 있음 2. predicate로 조건(데이터가 존재할때까지)
// 락을 잡아서 들어갔다가, 조건을 체크해서
// 만약 조건을 만족하지 않으면
// 락을 풀어주고, 잠든다.
// push연산 수행되면 (시그널(notify_one)의 발생), 일어나서 락을 잡고 다시 실행됨
outValue = std::move(_stack.top());
_stack.pop();
}
private:
stack<T> _stack;
mutex _mutex;
condition_variable _condVar;
};

 

이로써, busy waiting 문제를 해결했다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함