본문 바로가기

진리는어디에

Condition Variables

condition variables와 mutex를 같이 사용할 경우 condition variable은 임의의 상태(condition)이 발생하길 기다리는 race-free 한 방법을 제공 한다.

condition과 mutex의 사용에 대한 간략한 개념을 이야기 하자면 :

  • condition 자체는 mutex와 함께 사용되어야만 한다.
  • mutex는 condition variable이 wait로 넘어가기 전의 초기화라던지 기타 등등에 대한 concurrency를 보장한다.

condition variable 초기화

condition variable이 사용되기 전에 역시 다른 동기화 객체와 마찬가지로 초기화라는 것이 필요하다.
C/C++에서 condition variable은 pthread_cond_t 데이터 타입으로 표현 된다. 초기화 방법은 두 가지가 있는데 한가지는PTHREAD_COND_INITIALIZER(static하게 정해져 있는 변수)이고 다른 한가지는 pthread_cond_init(dynamic하게 할당하는 함수)가 있다. condition variable을 해제 할때는 pthread_mutex_destroy함수를 사용한다.

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);
  • return 0 if OK, error number on failure
  • 만일 별다른 옵션을 줄 필요가 없다면 attr은 단순히 NULL로 설정 한다.
    ※ 대부분 NULL이면 충분하다. 그리고 Linux Thread에서는 attribute를 지원하지 않기 때문에 attr 값은 무시된다.

condition 발생 대기

pthread_cond_wait 를 이용해 conditions이 발생(나중에 나올 pthread_cond_signal함수를 호출하는 것) 하기를 기다릴 수 있다.

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,
                           const struct timespec *restrict timeout);
  • return 0 if OK, error number on failure

pthread_cond_wait에 넘겨지는 mutex는 condition variable을 보호하며 함수에 lock을 건다. 이러한 과정을 거치면 호출 thread를 waiting thread list에 대기 시키고, 자동적으로 mutex를 해제한다(이말인 즉슨 pthread_cond_wait 이후 코드들이 계속 진행 된다는 말이다). thread는 이 과정에서 sleep에 빠지게 되므로 CPU 점유율을 먹지 않고, 더 이상 thread를 진행 하지 않으므로 mutex가 풀린다고 해서 concurrency에 문제가 생기지는 않는다.

pthread_cond_timedwait는 pthread_cond_wait와 동일 하게 동작하지만 timeout이라는 기능을 넣을 수 있다. timeout으로 넘겨지는 timespec 구조체는 아래와 같다.

struct timespec 
{
    time_t tv_sec;   /* seconds */
    long   tv_nsec;  /* nanoseconds */
};

이 구조체를 이용함에있어서 우리는 상대적 시간이 아니라 절대적 시간을 적어 줘야 한다. 예를 들어서 우리가 3분을 기다릴 것이라고 가정 한다면, timespec 구조체에 3분이라고 표시하는 대신, '현재시간+3분'이라고 적어 줘야 한다는 것에 유의 해야 한다.

만일 아무런 condition의 발생이 없이 timeout이 된다면, pthread_cond_timedwait 함수는 mutex를 재획득하고ETIMEDOUT을 리턴 할 것이다. pthread_cond_wait 또는 pthread_cond_timedwait 에서 정상적으로 리턴하게 된다면 다른 thread가 이미 run하고 condition을 이미 바꾸었는지에 대해서 condition을 다시 테스트해야 할 필요가 있다.

condition 발생 알림

대개 하던 condition이 발생했다는 것을 알리는 것은 아래의 두 함수가 있다. pthread_cond_signal 함수는 하나의 thread를 waiting 상태에서 깨우는 것이고 pthread_cond_broadcast 는 모든 thread를 wait 상태에서 깨우는 것이다.

The POSIX specification allows for implementations of pthread_cond_signal to wake up more than one thread, to make the implementation simpler.

#include <pthread.h>  

int pthread_cond_signal(pthread_cond_t *cond);  

int pthread_cond_broadcast(pthread_cond_t *cond); 
  • Both return: 0 if OK, error number on failure

주의 사항은 pthread_cond_signal 또는 pthread_cond_broadcast 을 호출하는 것은 condition이 변경 되고 난 뒤에 호출되어야만 한다는 것이다. 예를들어 queue에 값이 있는 것을 확인 하는 thread 같은 경우는 queue에 값을 넣고 난 뒤에 condition을 변경 해야만 한다.

Example

Condition은 ThreadSafeQueue의 상태를 나타내는 변수가 된다. while 루프 안에서 condition의 상태를 검사 할 것이고 queue에 data를 넣게 되면 condition의 상태를 만족 시키게 된다. 여기서 signal 함수를 꼭 mutex 사이에 위치 시켜야 할 필요는 없다. pthread_cond_signal 함수를 호출 하기 전에 queue에 data가 들어 갔다는 사실만 확정 되면 된다. Condition을 체크하는 부분이 while 루프 안에 있기 때문에 thread가 깨어나고, queue가 비었는지 확인하고, 다시 waiting 상태로 들어가고 하는 비효율 적인 polling 방법은 사용되지 않을 것이다.

#include <pthread.h>
#include <queue>

template<class T> 
class ThreadSafeQueue 
{
public :
    ThreadSafeQueue() 
    {
        pthread_mutex_init(&m_mutex, NULL);
        pthread_cond_init(&m_cond, NULL);
    }
    
    ~ThreadSafeQueue() 
    {
        pthread_cond_destroy(&m_cond);
        pthread_mutex_destroy(&m_mutex);
    }
    
    void enqueue(T t) 
    {
        pthread_mutex_lock(&m_mutex);
        m_queue.push(t);
        pthread_mutex_unlock(&m_mutex);
        pthread_cond_signal(&m_cond);
    }
    
    bool dequeue(T* t) 
    {
        pthread_mutex_lock(&m_mutex);
        while(m_queue.empty()) 
        {
            pthread_cond_wait(&m_cond, &m_mutex);
        }
        *t = m_queue.front();
        m_queue.pop();
        pthread_mutex_unlock(&m_mutex);
        return true;
    }
private :
    pthread_mutex_t m_mutex;
    pthread_cond_t  m_cond;
    std::queue<T>   m_queue;
};

부록 1. 참고

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!