Server

Condition Variable

HYuk 2026. 2. 26. 01:24
728x90

멀티스레드 환경에서 스레드 간의 협업을 설계할 때, 무작정 기다리는 것(Busy-wait)은 자원 낭비입니다.

유저 레벨에서 효율적으로 스레드 간 신호를 주고받을 수 있는 Condition Variable(조건 변수)가 있습니다.

 

Condition Variable은 유저 레벨 오브젝트(User-Level Object)입니다.

  • 가벼운 동기화: 커널 오브젝트(Event, Semaphore 등)가 프로세스 간(Inter-process) 동기화가 가능해 무거운 커널 모드 전환을 동반하는 반면, Condition Variable은 동일한 프로세스 내 스레드 간 동기화에 최적화되어 있어 상대적으로 가볍고 빠릅니다.
  • 배타적 제어와 신호: 단순히 "기다려라"가 아니라, 특정 "조건"이 만족될 때까지 스레드를 효율적으로 휴식 상태로 만듭니다.

가장 표준적인 큐(Queue) 기반의 생산자-소비자 모델입니다.

queue<int32> q;
mutex m;
condition_variable cv;

void Producer()
{
	while (true)
	{
		{
			unique_lock<mutex> lock(m);
			q.push(1);
		}
		cv.notify_one();
	}

}
void Consumer()
{
	while (true)
	{
		unique_lock<mutex> lock(m);
		// lock 잡고, 조건 확인
		// 1) 조건이 만족 시, 이어서 진행
		// 2) 조건을 만족하지 않는다면, lock을 풀고 wait
		// cv에서 unique_lock을 받는 이유는 경우에 따라 풀어줘야 하기 때문
		cv.wait(lock, []() {return q.empty() == false; });
		{
			int32 data = q.front();
			q.pop();
		}
	}

}

 

코드를 보면 lock_guard가 아닌 unique_lock을 사용합니다.

그 이유는 Condition Variable의 작동 방식 때문입니다.

1) 대기 상태에 들어갈 때 락을 해제(Unlock)하여 다른 스레드(Producer)가 접근할 길을 열어줍니다.

2) 신호를 받고 깨어날 때 다시 락을 획득(Lock)하여 안전하게 자원을 점유합니다.

이렇게 자유자재로 락을 걸고 풀 수 있는 유연함이 필요하기 때문에 unique_lock이 필수적입니다.

 

* 로직

 

  • 생산자(Producer): 락을 잡고 큐에 데이터를 안전하게 push한 뒤 락을 해제합니다. 이후 notify_one()을 통해 잠들어 있는 소비자에게 신호를 보냅니다.
  • 소비자(Consumer) 대기: wait 함수에 진입합니다. 만약 큐가 비어있다면, 잡고 있던 락을 스스로 풀고 대기 모드로 들어갑니다.
  • 소비자 재개: 생산자의 신호를 받으면 소비자가 깨어납니다. 이때 자동으로 다시 락을 획득한 뒤 조건을 검사합니다.
  • 조건 확인: q.empty() == false가 참이라면 비로소 아래의 팝(Pop) 로직을 수행합니다.

 

728x90