학부 내용 정리/[ 2-1 ] 운영체제

[ OS ] Condition Variables

haena02 2022. 6. 12. 18:12
반응형

조건이 성립하기 전까지 기다리는 것은 스레스에서 굉장히 유용하다. 하지만 이는 CPU 사이클이 낭비된다.

이를 해결할 수 있는 것은 Condition Variables이다.

 

Condition Variables는 큐이다. 스레드가 조건에 성립하지 않아 실행되지 않을 때 큐에 배치된다. 다른 스레드가 상태를 바꾸고 큐에 대기중인 스레드를 깨워 계속 진행할 수 있다.

 

pthread_cond_wait(); // 스레드가 잠들 때 사용
pthread_cond_signal();  // 자는 애를 깨움

함수는 위와같으며 인자로는 mutex가 들어간다. 

 

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 thr_join() {
	pthread_mutex_lock(&m);
    while (done == 0)
		pthread_cond_wait(&c, &m);  //  lock을 풀고 큐에 있는 잠들고 다시 lock
	pthread_mutex_unlock(&m);
}


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

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

위 코드는  Condition Variables 을 사용한 예시이다. 

done 변수는 child와 parent의 순서를 고정해주기 위해 사용되었고, race condition을 방지하여 lock을 사용하였다. 

 

1. Producer/Consumer Problem

 

Producers: 한정된 버퍼안에 데이터를 넣는 역할

Consumers: 한정된 버터에서 데이터를 가져오는 역할

 

이와같은 역할을 하는 예시로는 pipe와 web servers가 있다.

한정된 버퍼는 공유 리소스이기 때문에 동기화된 접근이 필요하다.

 

int buffer; // single buffer
int count = 0; // initially, empty
cond_t empty, fill;// 한개만 하면 깨울 때 cpu 마음대로 깨움
mutex_t mutex;

void put(int value) {
	assert(count == 0); // 0이면 프로그램 지속
	count = 1;
	buffer = value;
}
int get() {
	assert(count == 1); // 1이면 프로그램 지속
	count = 0;
	return buffer;
}

void *producer(void *arg) {
	int i;
	for (i = 0; i < loops; i++) {
		pthread_mutex_lock(&mutex); // p1
        //while 대신 if 문으로 작성 시 깨어났을 때 0이라고 보장 못함
		while (count == 1) // p2
        	//empty 큐에 넣기! 큐가 꽉차서 들어감
			pthread_cond_wait(&empty, &mutex); // p3
		put(i); // p4
        // fill 큐에 있는 애 깨우기
		pthread_cond_signal(&fill); // p5
		pthread_mutex_unlock(&mutex); // p6
	}
}

void *consumer(void *arg) {
	int i;
	for (i = 0; i < loops; i++) {
		pthread_mutex_lock(&mutex); // c1
         //while 대신 if 문으로 작성 시 깨어났을 때 1이라고 보장 못함
		while (count == 0) // c2
        	//empty 큐에 넣기! 큐가 비어서 들어감
			pthread_cond_wait(&fill, &mutex);  // c3
		int tmp = get(); // c4
        // empty 큐에 있는 애 깨우기
		pthread_cond_signal(&empty);  // c5
		pthread_mutex_unlock(&mutex); // c6
		printf("%d\n", tmp);
	}
}

여기서의 핵심은 Condition Variables을 소비자용과 생산자용 두개를 만든다는 것이다. 위코드는 버퍼가 1일 때 코드이다.

 

다음으로는 버퍼의 크기가 1개보다 클 때의 코드이다. 

일단 버퍼는 circle 하게 관리된다.

 

int buffer[MAX];
int fill_ptr = 0;
int use_ptr = 0;
int count = 0;
cond_t empty, fill;
mutex_t mutex;


void put(int value) {
	buffer[fill_ptr] = value;
	fill_ptr = (fill_ptr + 1) % MAX;  // circle로 관리하기 위함
	count++;
}
int get() {
	int tmp = buffer[use_ptr];
	use_ptr = (use_ptr + 1) % MAX;  // circle로 관리하기 위함
	count--;
	return tmp;
}

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

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

 

버퍼가 하나일 때와 크게 달라진 점은없다. 인덱스 번호 부여해주기랑 꽉찼을 때 비었을 때 관리만 잘 해주면 된다.

반응형

'학부 내용 정리 > [ 2-1 ] 운영체제' 카테고리의 다른 글

[ OS ] Common Concurrency Problems  (0) 2022.06.13
[ OS ] Semaphores  (0) 2022.06.13
[ OS ] Lock-based Concurrent Data Structures  (0) 2022.06.12
[ OS ] Locks_slides  (0) 2022.06.12
[ OS ] Concurrency and Thread  (0) 2022.06.11