ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 조건 변수, 생산자/소비자 문제
    운영체제 2024. 4. 18. 21:51

    락은 상호배제를 위해서

    조건변수는 동기화를 위해서

     

    void *child(void *arg) {
    	printf("child\n");
        return NULL;
    }
    
    int main(int argc, char *argv[]) {
    	printf("parent: begin\n");
        pthread_t c;
        pthread_create(&c, NULL, child, NULL);
        printf("parent: end\n");
        return 0;
    }

    부모 스레드와 자식 스레드 중 무엇이 먼저 끝날지 알 수 없음.

     

    volatile int done = 0;
    
    void *child(void *arg) {
    	printf("child\n");
        done = 1;
        return NULL;
    }
    
    int main(int argc, char *argv[]) {
    	printf("parent: begin\n");
        pthread_t c;
        pthread_create(&c, NULL, child, NULL);
        while(done == 0)
        	;
        printf("parent: end\n");
        return 0;
    }

    1. 변수로 비지 웨이팅하는 방법

     

    소프트웨어적으로 해결하는 방식, 자식이 하나면 작동은 함

    문제: 스핀이라 낭비가 심하고(비효율적), 자식이 여러개면 부정확

     

    int done = 0;
    pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t c = PTHREAD_COND_INITIALIZER;
    
    void thr_exit() {
    	pthread_mutex_lock(&m);
        done = 1;
        pthread_cond_signal(&c);
        pthread_mutex_unlock(&m);
    }
    
    void *child(void *arg) {
    	printf("child\n");
        thr_exit();
        return NULL;
    }
    
    void thr_join() {
    	pthread_mutex_lock(&m);
        while( done == 0 )
        	pthread_cond_wait(&c, &m);
        pthread_mutex_unlock(&m);
    }
    
    int main(int argc, char *argv[]) {
    	printf("parent: begin\n");
        pthread_t p;
        pthread_create(&p, NULL, child, NULL);
        
        thr_join();
        printf("parent: end\n");
        return 0;
    }

    조건 변수의 예시..

    중요한 점: wait 앞 뒤로 락, 언락은 명시적으로 해줘야 하고, while 문으로 변수(done)를 확인해줘야 함

     

    문제 상황 1) state variable (done) 안씀

    void thr_exit() {
    	pthread_mutex_lock(&m);
        pthread_cond_signal(&c);
        pthread_mutex_unlock(&m);
    }
    
    void thr_join() {
    	pthread_mutex_lock(&m);
        pthread_cond_wait(&c, &m);
        pthread_mutex_unlock(&m);
    }

    경우 1: 부모가 먼저 실행되는 경우

    부모는 기다리고, 자식이 끝난 다음에 다시 깨어나서 실행된다.

     

    경우 2: 자식이 먼저 수행되는 경우

    자식이 시그널을 보냈는데 부모는 애초에 wait에 들어가지 않았고,

    자식이 끝난 다음에야 wait에 들어가서 깨어나지 않게 되는 경우가 발생

     

    문제 상황 2)

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

    1. 일단 락이 없어서  상호배제가 안됨

    2. while문이 아니면 waiting 중이던 스레드가 다시 깨어났을 때 done이 조건에 맞는지 재확인이 안됨


    생산자, 소비자 문제

    생산자는 만들어서 버퍼에 넣어주고 소비자는 버퍼(크기 1)에서 꺼내는 상황을 가정

     

    int buffer;
    int count = 0;
    
    void put(int value) {
        assert(count == 0);
        count = 1;
        buffer = value;
    }
    int get() {
        assert(count == 1);
        count = 0;
        return buffer;
    }
    
    void *producer(void *arg) {
        int i;
        int loops = (int)arg;
        for(i = 0; i < loops; i++) {
        	put(i);
        }
    }
    
    void *consumer(void *arg) {
    	while(1) {
            int tmp = get();
            printf("%d\n", tmp);
        }
    }

    쉐어링을 고려하지 않은 코드

     

    cond_t cond;
    mutex_t mutex;
    
    void *producer(void *arg) {
        int i;
        for( i = 0; i < loops; i++ ) {
            pthread_mutex_lock(&lock);
            if(count == 1)
                pthread_cond_wait(&cond, &mutex);
            put(i);
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&mutex);
        }
    }
    
    void *consumer(void *arg) {
        int i;
        for( i = 0; i < loops; i++ ) {
            pthread_mutex_lock(&mutex);
            
            if(count == 0)
                pthread_cond_wait(&cond, &mutex);
                
            int tmp = get();
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&mutex);
            printf("%d\n", tmp);
        }
    }

    부정확한 코드

    소비자가 여럿일 때, 이미 if 문을 지나 대기하고 있던 소비자가 다른 소비자에 의해 깨워지면

    count에 대한 검사가 없이 실행됨 -> 버퍼가 비어있는데 가져가려고 할 가능성이 존재함

     

    => while 문으로 검사를 해줘야 함

     

    cond_t cond;
    mutex_t mutex;
    
    void *producer(void *arg) {
        int i;
        for( i = 0; i < loops; i++ ) {
        	pthread_mutex_lock(&mutex);
            while( count == 1 )
            	pthread_cond_wait(&cond, &mutex);
            put(i);
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&mutex);
        }
    }
    
    void *consumer(void *arg) {
    	int i;
        for( i = 0; i < loops; i++ ) {
        	pthread_mutex_lock(&mutex);
            while( count == 0 )
            	pthread_cond_wait(&cond, &mutex);
            int tmp = get();
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&mutex);
            printf("%d\n", tmp);
        }
    }

    이래도 문제가 있음

    생산자가 하나, 소비자가 여럿이라 할 때,

    생산자가 슬립 중인데 소비자가 소비하고 다른 소비자를 깨우면 생산자에 시그널을 보낼 수가 없어진다.

     

     

    cond_t empty, fill;
    mutex_t mutex;
    
    void *producer(void *arg) {
        int i;
        for( i = 0; i < loops; i++ ) {
        	pthread_mutex_lock(&mutex);
            while( count == 1 )
            	pthread_cond_wait(&empty, &mutex);
            put(i);
            pthread_cond_signal(&fill);
            pthread_mutex_unlock(&mutex);
        }
    }
    
    void *consumer(void *arg) {
        int i;
        for( i = 0; i < loops; i++ ) {
        	pthread_mutex_lock(&mutex);
            while( count == 0 )
                pthread_cond_wait(&fill, &mutex);
                
            int tmp = get();
            pthread_cond_signal(&empty);
            pthread_mutex_unlock(&mutex);
            printf("%d\n", tmp);
        }
    }

    조건 변수를 2개 두고, while 문에서 wait하도록 하여 해결된다.


     

    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++;
    }
    
    int get() {
        int tmp = buffer[use_ptr];
        use_ptr = (use_ptr + 1) % MAX;
        count--;
        return tmp;
    }
    cond_t empty, fill;
    mutex_t mutex;
    
    void *producer(void *arg) {
        int i;
        for( i = 0; i < loops; 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) {
        int i;
        for( i = 0; i < loops; i++ ) {
            pthread_mutex_lock(&mutex);
            while( count == 0 )
                pthread_cond_wait(&fill, &mutex);
            int tmp = get();
            pthread_cond_signal(&empty);
            pthread_mutex_unlock(&mutex);
            printf("%d\n", tmp);
        }
    }

    버퍼의 크기가 늘어난 일반적인 버전


    pthread_cond_broadcast()

    pthread_cond_signal()은 기다리고 있던 스레드 하나만 깨움

    pthread_cond_broadcast()는 조건 변수 해당하는 스레드 다 깨움

     

    왜 이것이 필요한가

    메모리가 충분하지 않은 상황(꽉 찼다고 가정)

    T1: 100바이트 필요

    T2: 50바이트 필요

    free로 인해 발생한 시그널 -> 여유 공간이 50바이트 생겼다고 가정

     

    pthread_cond_signal()을 쓴다고 할 때,

    경우1: T2가 깨는 경우 -> T2 실행

    경우2: T1이 깨는 경우

    -> 여유공간이 부족해서 T1 슬립, T2는 깬 적이 없으니 실행 불가

     

    => pthread_cond_broadcast()로 일단 다 깨우면 T2가 실행이 가능

    '운영체제' 카테고리의 다른 글

    데드락  (0) 2024.05.06
    Semaphore (세마포)  (0) 2024.04.18
    Lock-based Concurrent Data Structure  (0) 2024.04.18
    Lock의 구현  (0) 2024.04.11
    멀티 스레드 / 경쟁 상태, 공유자원, 임계영역, 상호배제  (0) 2024.04.10
Designed by Tistory.