Review
이전 포스팅에서 지속적으로 언급했듯이, 멀티프로세스 또는 멀티쓰레드 환경에서 공유자원을 사용하다보면 race condition 이 발생할 수 있고 이를 해결하기 위해서는 동기화가 필요하다. 동기화에는 상호배제(Mutual Exclusion) 전략과 조건 동기화(Condition Synchronization) 전략이 있다. 지난 포스팅까지는 상호배제의 관점에서 동기화를 알아보았다.
상호배제는 공유자원은 한번에 하나씩 사용하는데 집중한다. 비슷하지만 조건동기화는 Critical Section에 대기하는 프로세스(또는 쓰레드)를 Ordering 하는 전략이다.
조건 동기화
조건동기화는 특정 조건을 만족할때까지 쓰레드(또는 프로세스)를 대기시킨다. 그리고 조건을 만족될 때 해당 쓰레드를 깨워서 실행시키는 방법이다. 조건 변수(conditional variable)를 통해서 작동한다. 조건 변수에 대한 특정 condition 을 만족시킬때 실행하도록 하고, 아니면 대기할 수 있도록 wait(), signal() 등의 연산이 존재한다.
Condition Variables
아래와 같이 child 가 다 수행되기를 기다려야하는 상황을 생각해보자.
//[Thread-1]
int main(int argc, char *argv[]) {
pthread_t c;
printf("parent: begin\n");
pthread_create(&c, NULL, child, NULL);
while (done == 0) { } // condition variable! 자식 스레드가 변경하기를 기다림!
printf("parent: end\n");
return 0;
}
//[Thread-2]
void * child (void *arg) {
printf("child\n");
pthread_mutex_lock(&m);
done = 1; // condition variable!
pthread_mutex_unlock(&m);
return NULL;
}
이 때에는 특정 상태변수(done)를 두어서 해결할 수 있다. 이 때 done 과 같은 상태변수를 condition variable 이라고 하고, 다음과 같은 특성을 갖는다.
- event(진행조건이 만족될 때 발생) 가 발생하기 전까지 대기시키는 역할을 한다.
- waiting queue 와 같은 역할
- 공유변수이기 때문에 동기화(상호배제)가 필수적이다.
Condition Operation
- wait() : critical section 에 진입할 때의 조건을 만족하지 않으면, 대기큐로 이동해서 대기한다.
- signal() : 대기큐에 있는 스레드(프로세스) 중 하나를 깨운다. 모든 대기 스레드를 깨우는 연산도 있다.
pthread.h API 예시
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m);
int pthread_cond_signal(pthread_cond_t *c);
// Returns: 0 if OK, error number on error
c 에서 제공하는 pthread 라이브러리를 보면 위와 같은 조건 동기화 함수를 제공한다. pthread_cond_t * 는 일종의 대기큐를 의미한다. pthread_mutex_t * 라는 lock 변수는 wait() 함수에 인자로 전달되어어야하는데, 위에서 말했다시피 조건변수도 공유변수이기 때문에 상호배제되어야하기 때문이다. 따라서 조건동기화의 wait() 함수에는 mutex 가 항상 붙어다녀야한다고 생각하면 된다. 아래 join() 구현 예제에서 더 자세히 살펴보자.
<join() 구현 예제>
join() 함수는 보통 부모스레드가 자식 스레드를 기다릴 때 사용하는 함수다. 부모스레드가 join() 을 실행하게 되면, 자식 스레드가 실행완료했다는 시그널을 보낼 때까지 대기하게 된다.
조건 동기화를 구현하기 위해서는 3가지 필수 요소가 존재한다. 이 요소들이 왜 필요한지는, broken case 를 생각해보면 된다.
(ex. state viarable 이 없을 때 어떻게 동기화가 깨지는 지)
- State Variable : 상태변수. 이 예제에서는 done 변수이다.
- Lock for State Variable : 상태변수 또는 조건변수도 스레드 간 공유자원이기 때문에 상호배제가 필요하다.
- Loop checking a condition : 스케줄링 문제로 인해 if 가 아닌 while loop 를 이용한 확인이 필요하다. (의미는 if 와 동일하다.)
(1) 부모 스레드
//[Thread-1]
int main(int argc, char *argv[]) {
pthread_t c;
pthread_create(&c, NULL, child, NULL);
thr_join ();
return 0;
}
void thr_join() {
pthread_mutex_lock(&m); // done 변수에 대한 상호배제
while (done == 0)
pthread_cond_wait(&c, &m);
pthread_mutex_unlock(m); // done 변수에 대한 상호배제
}
pthread_cond_wait(&c, &m) 을 주목할 필요가 있다. 여기서 lock 변수 m을 넘겨주는 이유가 구체적으로 드러난다.
wait() 함수를 만나면 부모스레드는 대기큐로 들어가서 잠든다. 하지만 문제는 Critical Section 안에서 잠든다는 것이다! 이 말인 즉슨, 공유변수에 대한 lock 을 풀어주지 않고 잠드는 것데, 마치 화장실에 들어가서 잠드는 것과 같다. 아무도 화장실을 이용할 수 없게 된다. 공유변수에 대한 lock 을 해제하고 잠들어야한다. 그래야 자식 스레드가 done 에 접근할 수 있고, 부모를 깨워줄 수 있기 때문이다. pthread_cond_wait(&c, &m) 를 실행하면 다음과 같은 일이 벌어진다.
- 공유변수에 대한 lock 을 release 한다.
- 잠든다. (대기)
- 신호를 받고 깨어나면 다시 lock 을 잡는다.
정리하자면 os 에서는 wait() 함수를 실행할 때, 내부에서 lock 을 해제하고 잠든다음, 깨면 다시 lock 을 잡도록 되어있다.
(2) 자식 스레드
자식은 state variable 을 변경하고, 대기 중인 스레드에게 시그널을 보내서 깨운다.
[Thread-2]
void * child (void *arg) {
// do something
thr_exit ();
return NULL;
}
void * thr_exit() {
pthread_mutex_lock(&m); // done 변수에 대한 상호배제
done = 1;
pthread_cond_signal(&c);
pthread_mutex_unlock(&m); // done 변수에 대한 상호배제
}
조건동기화는 condition 을 만족시킬 때 critical section 에 들어가고, 만족시키지 못하면 대기하도록 하는 기법이다. 조건 동기화를 위해서는 (1) 상태변수 (2) lock (3) loop checking 이 필요함을 알고, pthread 를 이용하여 간단하게 join() 함수를 구현해보았다.
다음에는 조건동기화에서 발생하는 생산자-소비자 문제, Readers-Writers 문제에 대해서 알아보도록 하자.
'CS > Operating System' 카테고리의 다른 글
[전공생이 설명하는 OS] 동기화 - (5) Deadlock (0) | 2022.05.31 |
---|---|
[전공생이 설명하는 OS] 동기화 - (4) 생산자 소비자 문제 (0) | 2022.05.22 |
[전공생이 설명하는 OS] 동기화 - (2) 상호배제 전략 (Mutex) (0) | 2022.05.19 |
[전공생이 설명하는 OS] 동기화 - (1) 용어 및 개념정리 (0) | 2022.05.19 |
[전공생이 설명하는 OS] 쓰레드(Thread)와 동기화 문제 (3) | 2022.04.28 |