Computer Science/Operating System

[OS] 30장: Condition Variables

kyxxn 2024. 7. 3. 19:56
728x90

30장: Condition Variables

조건변수 개요

  1. pthread_cond_t c; 선언 + 초기화 필요

  2. wait와 signal을 통해 사용함

  3. wait() 호출에 Mutex 변수가 필요함

    pthread_cond_wait(pthread_cond_t* c, pthread_mutex_t *m);
    조건 변수도 공유변수이기 때문에.
    대기 상태로 갈 때 락을 해제함
    깨어날 때 두 번째 인자 m에 대한 락을 다시 획득

조건변수 사용 기초

int done = 0;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t c = PTHRREAD_COND_INITIALIZER;

void thr_exit() {
        pthread_mutex_lock(&m);
        done = 1;
        pthread_cond_signal(&c);
        pthread_mutex_unlock(&m);
}

void thr_join() {
        pthread_mutex_lock(&m);
        while (done == 0)
                pthread_cond_wait(&c, &m);
        pthread_mutex_unlock(&m);
}

void *child(void* arg) {
        printf("child");
        thr_exit();
        return NULL;
}

void main(int argc, char* argv[]) {
        printf("parent");
        pthread_t p;
        pthread_create_t(&p, NULL, child, NULL);
        thr_join();
        printf("end");
        return 0;
}

만약 Done이 없으면 ?

exit()에서 signal을 먼저 해버렸을 경우, join에서 계속 wait() 됨

부모가 못 깨어남

만약 mutex가 없으면 ?

void thr_exit() {
        done = 1;
        pthread_cond_signal(&c);
}

void thr_join() {
        if (done == 0)
                pthread_cond_wait(&c);
}

부모가 done==0에 의해 if문에 진입해서 wait 하려는데, 인터럽트 당함

자식이 done = 1;이랑 signal을 해버림

부모가 못 깨어남

생산자/소비자 문제

생산자 = 데이터 생산 후 버퍼에 둠
소비자 = 버퍼에서 데이터 가져다 씀

유한 버퍼 개념

정해진 크기의 버퍼에 생산자가 소비자가 연결되어 같이 작업함
공유 자원이므로 경쟁조건 발생 방지를 위해 동기화 필요

구현1: 조건 변수와 mutex 사용

cond_t cond;
mutex_t mutex;

void* producer(void* arg) {
        for (int i=0; i<loop; i++) {
                pthread_mutex_lock(&mutex); // p1
                if (count == 1)  // p2
                        pthread_cond_wait(&cond, &mutex); // p3
                put(i);  // p4
                pthread_cond_signal(&cond); // p5
                pthread_mutex_unlock(&mutex);  // p6
        }
}

void* consumer(void* arg) {
        for (int i=0; i<loop; i++) {
                pthread_mutex_lock(&mutex); // c1
                if (count == 0)  // c2
                        pthread_cond_wait(&cond, &mutex);  // c3
                int tmp = get();  // c4
                pthread_cond_signal(&cond); // c5
                pthread_mutex_unlock(&mutex);  // c6
                printf("%d", tmp);
        }
}

생산자 1개, 소비자 1개라면 문제가 발생하지 않음

그러나, 같은 타입이 2개 이상이 된다면 문제가 발생

  1. if문으로 되어 있어서 Wait()를 했을 때, Mesa Semantics 문제 해결 X
  2. cond 변수가 1개라 누가 깨어날 지 모른다는 점

Tc1이 wait()에 갔으므로 Tp가 버퍼에 넣어주면 Tc1이 깨어나서 써야 함

그러나, Tc2가 가져가버림

Tc1은 if문으로 wait()를 했기 때문에,
깨어나서 다음 줄 c4로 가버리게 됨. (이때 버퍼는 비어 있으므로 에러)

→ while문으로 구현함

구현2: 상태 변수 점검에 while 도입

Mesa Semantics 문제를 while을 통해 버퍼를 계속 검사하게 하여 해결함

cond_t cond;
mutex_t mutex;

void* producer(void* arg) {
        for (int i=0; i<loop; i++) {
                pthread_mutex_lock(&mutex); // p1
                while (count == 1)  // p2
                        pthread_cond_wait(&cond, &mutex); // p3
                put(i);  // p4
                pthread_cond_signal(&cond); // p5
                pthread_mutex_unlock(&mutex);  // p6
        }
}

void* consumer(void* arg) {
        for (int i=0; i<loop; i++) {
                pthread_mutex_lock(&mutex); // c1
                while (count == 0)  // c2
                        pthread_cond_wait(&cond, &mutex);  // c3
                int tmp = get();  // c4
                pthread_cond_signal(&cond); // c5
                pthread_mutex_unlock(&mutex);  // c6
                printf("%d", tmp);
        }
}

그러나 두 번째 문제가 발생함 (누가 깨어날 지 지정을 안 해줌)

  1. Tc2와 P가 wait() 상태일 때, Tc1이 버퍼 가져다 쓰고 signal을 보냄
  2. P가 깨어나야 하는데, Tc2가 깨어나버림
  3. Tc2는 버퍼가 비어 있으므로 c3에서 Wait()함
  4. 3개 모두 다 wait() 상태가 됨

구현3: 두 개의 조건 변수와 while 사용

cond_t empty, fill;
mutex_t mutex;

void* producer(void* arg) {
        for (int i=0; i<loop; i++) {
                pthread_mutex_lock(&mutex); // p1
                while (count == 1)  // p2
                        pthread_cond_wait(&empty, &mutex); // p3
                put(i);  // p4
                pthread_cond_signal(&fill); // p5
                pthread_mutex_unlock(&mutex);  // p6
        }
}

void* consumer(void* arg) {
        for (int i=0; i<loop; i++) {
                pthread_mutex_lock(&mutex); // c1
                while (count == 0)  // c2
                        pthread_cond_wait(&fill, &mutex);  // c3
                int tmp = get();  // c4
                pthread_cond_signal(&empty); // c5
                pthread_mutex_unlock(&mutex);  // c6
                printf("%d", tmp);
        }
}

버퍼 추가해서 효율적인 동작

원형 큐 사용

int buffer[MAX];
int fill_ptr = 0;
int use_ptr = 0;
int count = 0;

void put(int value) {
        buffer[fill_ptr] = value;
        fill_ptr = (fill_ptr + 1) % MAX;
        count++;
}

void get() {
        int tmp = buffer[use_ptr];
        use_ptr = (use_ptr + 1) % MAX;
        count--;

        return tmp;        
}
pthread_cond_t empty, fill;
pthread_mutex_t mutex;

void* producer(void* arg) {
        for (int i=0; i<loop; i++) {
                pthread_mutex_lock(&mutex);
                while (count == MAX)
                        pthread_cond_wait(&empty, &mutex);
                put(i);
                pthread_cond_signal(&fill);
                pthread_mutex_unlock(&mutex);
        }
}

void* consumer(void* arg) {
        for (int i=0; i<loop; i++) {
                pthread_mutex_lock(&mutex);
                while (count == 0)
                        pthread_cond_wait(&fill, &mutex);
                int tmp = get();
                pthread_cond_signal(&empty);
                pthread_mutex_unlock(&mutex);
        }
}

Convering Condition (포함 조건)

멀티 스레드 환경에서 메모리 할당 생각

  • 빈 공간이 없다고 가정

문제 상황

  1. A 스레드가 100의 메모리 할당 필요
  2. B 스레드가 10의 메모리 할당 필요
  3. 50의 여유 공간이 생겼다. 어떤 스레드르 깨울까 ?

→ signal() 대신, broadcast를 사용하여 적절한 스레드가 동작하게 함

void free(void* ptr, int size) {
        pthread_mutex_lock(&m);
        ..
        ~~pthread_cond_signal(&c);~~ pthread_cond_broadcast(&c); 가 되어야 함
        pthread_mutex_unlock(&m);
}