다양한 기록

조건 변수, 생산자/소비자 문제 본문

운영체제

조건 변수, 생산자/소비자 문제

라구넹 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