반응형
조건이 성립하기 전까지 기다리는 것은 스레스에서 굉장히 유용하다. 하지만 이는 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 |