조건 변수 사용 방법

 조건 변수는 어떠한 상황에서 사용할 수 있을까요? 예를 들면 로그를 처리하기 위한 생산자 스레드와 소비자 스레드가

 있다고 가정합니다. 생산자 스레드에서는 로그 데이터를 생성해서 Queue 컨데이터에 삽입합니다. 

 소비자 스레드에서는 Queue에 데이터가 들어오면 데이터를 전달 받아서 파일에 기록합니다. 이런식으로 동작한다면 

 Queue 컨데이터는 스레드간 공유 자원이 되기 때문에 락을 통해서 보호 해야 합니다. 

 또한 소비자 스레드 입장에서는 Queue에 데이터가 들어 있을 경우에만 동작을 해야 합니다. Queue에 데이터가 들어

 있지 않다면 아무 행동을 하지 않고 대기하여 장비의 리소스를 소모하지 않는 편이 가장 이상적입니다. 

 효율적으로 동작하기 위해서는 조건 변수를 사용하는 SleepConditionVariableXXX 함수를 호출합니다.

 SleepConditionVariable의 인자로 지정된 락 객체는 자동으로 Release 되며 지정된 조건 변수에 알림이 오기 전까지

 해당 스레드는 sleep 상태로 전환 되어 대기합니다. (Sleep상태에서는 리소스를 소모X)

 생산자 스레드는 Queue에 데이터를 삽입하고 소비자 스레드에게 알리기 위해서 WakeConditionVariable함수를 

 호출하면 대기하고 있던 소비자 스레드에게 알림이 전달되고 스레드는 깨어나 이전에 해제한 락을 다시 획득하고

SleepConditionVariableXXX 함수 이후 로직을 실행합니다.  

조건 변수 사용방법

 msdn의 조건변수에 대한 내용입니다.

https://docs.microsoft.com/en-us/windows/win32/sync/condition-variables

 

Condition Variables - Windows applications

Condition Variables In this article --> Condition variables are synchronization primitives that enable threads to wait until a particular condition occurs. Condition variables are user-mode objects that cannot be shared across processes. Condition variable

docs.microsoft.com

 msdn의 문서 내용을 번역해서 올립니다. 개인적으로 번역한 내용이라 불충분한 점이 있으므로

 발견 시 댓글로 요청 주시면 수정하겠습니다. 

 조건 변수는 특정 조건이 발생할 때까지 스레드가 대기하도록 설정하는 동기화의 기본 요소입니다.

 조건 변수는 프로세스간의 공유를 하지 못하는 유저 모드 객체입니다. 

 조건 변수는 스레드가 원자적으로 락을 해제하고 sleep상태로 들어가는 것을 가능하게 합니다.

 조건 변수는 크리티컬 섹션슬림 리더 락과 같이 사용 할 수 있습니다. 조건 변수는 "wake one" 또는"wake all"라는

 대기중인 스레드를 위한 연산을 지원합니다. wake로 스레드가 깨어난 후에 스레드가 이전에 Sleep상태로 들어가기

 전에 해제 한 잠금을 다시 획득합니다.

 호출자는 CONDITION_VARIABLE 구조체를 할당해야 하고 구조체 변수를 InitializeConditionVariable을 호출하거나

 CONDITION_VARIABLE_INIT에 배정해서 초기화합니다.

 Windows Server 2003 and Windows XP : 조건 변수를 지원하지 않습니다. 

 조건 변수의 기능은 다음과 같습니다.

조건 변수 함수 설  명
InitializeConditionVariable 조건 변수를 초기화 합니다.
SleepConditionVariableCS 지정한 조건변수를 Sleep상태로 만들고 지정된 크리티컬 섹션을 원자적 작업으로 해제합니다.
SleepConditionVariableSRW 지정한 조건 변수를 Sleep상태로 만들고 지정된 SRW의 잠금을 원자적 작업으로 해제합니다.
WakeAllConditionVariable 지정한 조건 변수를 기다리는 모든 스레드를 깨웁니다.
WakeConditionVariable 지정한 조건 변수를 기다리는 하나의 스레드를 깨웁니다. 

다음 예제 코드는 조건 변수의 일반적인 사용 패턴을 보여줍니다.

CRITICAL_SECTION CritSection;
CONDITION_VARIABLE ConditionVar;

void PerformOperationOnSharedData()
{ 
   EnterCriticalSection(&CritSection);

   // Wait until the predicate is TRUE

   while( TestPredicate() == FALSE )
   {
      SleepConditionVariableCS(&ConditionVar, &CritSection, INFINITE);
   }

   // The data can be changed safely because we own the critical 
   // section and the predicate is TRUE

   ChangeSharedData();

   LeaveCriticalSection(&CritSection);

   // If necessary, signal the condition variable by calling
   // WakeConditionVariable or WakeAllConditionVariable so other
   // threads can wake
}

 예를 들어 리더/라이트 락 잠금 구현에서 TestPredicate 함수는 현재의 잠금 요청(&CritSection)이 기존의 소유자와

 호환될 수 있는지확인할 것입니다. 확인 후 락을 획득하거나 Sleep에 빠지게 됩니다.(TestPredicate True면 락 획득,

 TestPredicate False면 SleepConditionVariableCS함수를 통해서 꿈나라로)

 조건 변수는 spurious wakeups(명시적인 wake와 관련이 없는) and stolen wakeups에 영향을 받습니다.

 그러므로 sleep 작업 이후에 조건값(일반적으로 while loop에서)다시 체크해야 한다.

 (예제에서는 SleepConditionVariableCS 함수에서 깨어난 후 TestPredicate 함수를 또 체크합니다.))

 조건 변수와 관련된 잠금을 내부 또는 외부에서 WakeConditionVariable 또는 WakeAllConditionVariable 사용해

 다른 스레드를 깨울 수 있습니다. 컨텍스트 스위치를 횟수를 줄이려면 일반적으로 다른 스레드를 깨우기 전에 락을

 해제하는 것이 좋습니다. 보통은 동일한 잠금에 하나의 조건 변수를 사용하는 것보다 2개 이상의 조건 변수를

 사용하는 것이 편리합니다. 예를 들면 reader/writer 락의 구현은 단일 크리티컬 섹션을 사용하지만 reader와 writer에 

 대해서 각각 별개의 조건 변수로 구현할 수 있습니다.

spurious wakeups

 MSDN에서 조건 변수를 설명할 때 "spurious wakeups" 단어가 나오는데 명확히 어떤 뜻을 가지는지 확인해봅시다.

 아래는 위키피디아에 올라온 "spurious wakeups"의 설명입니다.

https://en.wikipedia.org/wiki/Spurious_wakeup

 spurious wakeups란 비정상적으로 깨어난 조건 변수 알림을 뜻 합니다. 일반적으로는 이해되지 않지만 

 예를 들면 신호를 기다리고 있는 조건 변수는 깨어날 스레드를 명시적으로 지정하지 않은 broadcast 신호에 의해서

 깨어 날 수 있습니다.

   while( TestPredicate() == FALSE )  // if( TestPredicate() == FALSE ) spurious wakeups문제 발생가능성 있음
   {
      SleepConditionVariableCS(&ConditionVar, &CritSection, INFINITE);
   }

예를 들면 위의 예제에서는 TestPredicate함수가 TRUE가 되는 상태에서 ConditionVar조건 변수가 깨어나길 원합니다. 

WakeAllConditionVariable(&ConditionVar);
WakeConditionVariable(&ConditionVar);

 하지만 다른 스레드에서 WakeAllConditionVariable이나 잘못된 WakeConditionVariable 호출을 통해서

 TestPredicate함수가 TRUE가 아닌 상태에서 ConditionVar 조건 변수가 깨어 날 수 있습니다. 그것을 예방하기 위해서

 while문을 통해서 조건 변수가 깨어난 후 다시 TestPredicate함수를 검사하도록 구현합니다.

 만일 spurious wakeups일 경우 스레드는 다시 Sleep 상태로 전환되어 대기합니다.

+ Recent posts