메뉴 건너뛰기


Developer > Application
6장. 조건변수(Condition Variable)
 
이번에는 유닉스 계열(POSIX)에서만 사용할 수 있는 또 다른 동기화 객체인 조건변수라는 것에 대해서 이야기 해보겠습니다.
 
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); 
int pthread_cond_signal(pthread_cond_t *cond); 
int pthread_cond_broadcast(pthread_cond_t *cond); 
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime); 
int pthread_cond_destroy(pthread_cond_t *cond);
 
조건 변수('condition variable')는 공유 데이터에 대한 특정 조건에 따라 쓰레드의 실행을 중지하거나 다시 실행시키는 역할을 하는 동기화 장치입니다.
조건변수라는 특별한 변수를 만들고 쓰레드는 이 조건변수가 신호를 받을 때까지 기다립니다. 다른 쓰레드에서 특정조건이 만족되었을 경우 조건 변수에 시그널을 보냅니다. 그러면 조건변수가 신호를 받기를 기다리던 쓰레드가 깨어나서 동작하는 방식입니다. 
조건 변수는 한 쓰레드가 조건 변수를 기다릴 준비를 하는 동안 (실제로 대기하기 전에) 다른 쓰레드가 해당 조건 변수에 시그널을 보내는 것과 같은 경쟁 상태를 방지하기 위해 항상 뮤텍스와 함께 사용되야 합니다.

pthread_cond_init()함수는 cond 조건 변수를 cond_attr 로 지정된 조건 변수 속성을 사용해서 초기화하거나, cond_attr 가 NULL 인 경우에는 기본 속성으로 초기화합니다. Linux에서는 cond_attr이 무시됩니다. 호환성 있는 코드를 만들려면 cond_attr을 NULL로 사용하는게 좋겠죠? 
뮤텍스에서와 마찬가지로 pthread_cond_t 타입의 변수는 PTHREAD_COND_INITIALIZER 상수를 이용하여 정적으로도 초기화 할 수 있습니다.

pthread_cond_signal()함수는 cond 조건 변수에 신호를 보내 이 cond조건변수를 기다리고 있는 쓰레드 중의 하나를 다시 시작시킵니다. 만약 cond 조건 변수를 기다리고 있는 쓰레드가 없다면, 아무 일도 일어나지 않습니다. 만약 cond 조건 변수를 기다리는 쓰레드가 여러 개라면, 그 중의 하나만 깨어나지만, 특정 쓰레드를 지정할 수는 없습니다. 
pthread_cond_broadcast()함수는 cond 조건 변수를 기다리고 있는 모든 쓰레드를 다시 시작시킵니다. 만약 cond 조건 변수를 기다리고 있는 쓰레드가 없다면, 아무 일도 일어나지 않습니다. 
pthread_cond_wait()함수는 조건변수가 신호를 받을 때까지 기다리는 역할을 합니다. 실제로 내부적으로는 pthread_unlock_mutex 를 통해 mutex 를 unlock하고 cond 조건 변수가 시그널을 받을 때까지 기다립니다. 조건변수가 신호를 받아 다시 쓰레드가 깨어 날때 pthread_cond_wait()함수는 pthread_lock_mutex 를 통해 mutex 에 다시 lock을 겁니다. 
pthread_cond_timedwait()함수는 pthread_cond_wait()과 같은 역할을 하지만 기다릴 시간을 지정할 수 있다는 것이 다른 점입니다. 타임아웃에 의해 pthread_cond_timedwait()함수가 풀려날때는 ETIMEDOUT 에러 코드를 반환합니다. 이 때 지정하는 abstime인자는 1970년 1월 1일 00:00:00 GMT 를 0으로 하여 설정하여야 합니다. 
pthread_cond_destroy()함수는 조건변수를 삭제하고, 조건 변수에 할당된 자원을 해제합니다. pthread_cond_destroy()함수가 실행될 때 어떤 쓰레드도 해당 조건 변수를 기다리고 있어서는 안됩니다. 만약 아직도 조건변수가 신호를 받기를 기다리는 쓰레드가 있다면 EBUSY라는 에러 코드를 리턴합니다. 참고로 Linux에서는 조건 변수에 할당되는 자원이 없으므로 pthread_cond_destroy()함수는 조건 변수를 기다리고 있는 쓰레드가 있는지 검사하는 일 외에는 아무 작업도 하지 않습니다. 
 
모든 조건 변수 관련 함수들은 성공시에 0을, 에러시에 0이 아닌 에러 코드를 반환합니다.
단, pthread_cond_init(), pthread_cond_signal(), pthread_cond_broadcast(), pthread_cond_wait()함수는 에러 코드를 반환하지 않습니다. 
pthread_cond_timedwait()함수는 성공시 0, 에러시 ETIMEDOUT 혹은 EINTR에러는 리턴할 수 있습니다. ETIMEDOUT에러는 조건 변수가 abstime 로 지정된 시간 내에 신호를 받지 못해서 타임아웃이 발생했다는 것을 의미합니다. EINTR에러는 pthread_cond_timedwait()함수가 다른 시그널에 의해 인터럽트 되었다는 것을 의미합니다. 
 
조건변수가 단순 뮤텍스와 크게 다른 점은 타임아웃을 사용하여 데드락을 미연에 방지할 수 있다는 것과 동시에 여러 쓰레드를 깨울 수 있다는 것입니다.
 
다음은 조건 변수를 사용한 예제를 보여드리겠습니다.
 
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <time.h> #include <pthread.h> int x=1, y=2; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; void *Thread1(void *arg) { int i, tmp; sleep(1); x=2; y=1; if (x > y) pthread_cond_broadcast(&cond); printf("Thread1 End\n"); return NULL; } void *Thread2(void *arg) { struct timeval now; struct timespec timeout; int retcode; pthread_mutex_lock(&mut); gettimeofday(&now); timeout.tv_sec = now.tv_sec + 5; timeout.tv_nsec = now.tv_usec * 1000; retcode = 0; if(x <= y) { printf("Thread2 Sleep\n"); retcode = pthread_cond_timedwait(&cond, &mut, &timeout); } if (retcode == ETIMEDOUT) { printf("TimeOut\n"); } else { printf("x=%d, y=%d\n", x, y); } pthread_mutex_unlock(&mut); printf("Thread2 End\n"); return NULL; } int main(int argc, char *argv[]) { pthread_t thread1; pthread_t thread2; pthread_create(&thread1, NULL, Thread1, NULL); pthread_create(&thread2, NULL, Thread2, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); pthread_mutex_destroy(&mut); pthread_cond_destroy(&cond); return 0; }
 
위의 예제는 다른 예제와 좀 다름으로 설명이 필요하겠네요.
 
우선 main()함수에서는 두개의 쓰레드를 만듭니다.
 
thread1에서는 1초간 기다렸다가 x와 y의 값을 x=2로 y=1로 변경시키고 조건변수에게 신호를 보냅니다.
 
thread2는 x가 y보다 작거나 같으면 대기합니다. 그러다가 thread1로 부터 신호를 받으면 깨어나서 다음을 수행하게 됩니다.
 
이렇게 해서 항상 thread1이 먼저 수행이 이루어 지고 다음으로 thread2가 수행됨을 확인할 수 있습니다.
 
위 코드의 수행결과는 다음과 같습니다.
 
Thread2 Sleep
Thread1 End
x=2, y=1
Thread2 End
 
조건변수는 의외로 간단하네요.
 
를 한번 읽어보세요.
위의 글도 이 URL을 많이 참고 했습니다.
 
Creative Commons License
Creative Commons License이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
Copyright 조희창(Nicholas Jo). Some rights reserved. http://bbs.nicklib.com