7 min read

OSTEP 30 Condition Variables

Table of Contents

μš°λ¦¬κ°€ 배운 β€˜λ½β€™ ν•˜λ‚˜λ§Œ κ°€μ§€κ³ λŠ” μ œλŒ€λ‘œ 병행 ν”„λ‘œκ·Έλž¨μ„ μž‘μ„±ν•  수 μ—†λ‹€. μ“°λ ˆλ“œκ°€ 계속 μ§„ν–‰ν•˜κΈ° 전에 νŠΉμ • 쑰건이 λ§Œμ‘±λ˜μ—ˆλŠ”μ§€ 검사가 ν•„μš”ν•œ κ²½μš°κ°€ μžˆλ‹€. 예λ₯Ό λ“€λ©΄ μžμ‹ μ“°λ ˆλ“œκ°€ μž‘μ—…μ„ λλƒˆλŠ”μ§€ μ—¬λΆ€λ₯Ό μ•Œ ν•„μš”κ°€ μžˆλ‹€. 이런 κ±Έ μ–΄λ–»κ²Œ κ΅¬ν˜„ν•  수 μžˆμ„κΉŒ?

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)
	; // spin
	printf(β€œparent: end\n ”);
	return 0;
}

μ΄λ ‡κ²Œ 곡유 λ³€μˆ˜λ‘œ κ΅¬ν˜„ν•  수 μžˆλ‹€ ν•˜μ§€λ§Œ λΆ€λͺ¨ μ“°λ ˆλ“œκ°€ spin ν•˜λ©΄μ„œ μžμ›μ„ λ‚­λΉ„ν•˜κ³  μžˆλ‹€. 이 방법 λŒ€μ‹  λΆ€λͺ¨ μ“°λ ˆλ“œκ°€ νŠΉμ • 쑰건이 λ§Œμ‘±λ λ•ŒκΉŒμ§€ μž μžλ©΄μ„œ κΈ°λ‹€λ¦¬λŠ” 것이 더 μ’‹λ‹€.

1. μ •μ˜μ™€ 루틴듀

쑰건이 참이 될 λ•ŒκΉŒμ§€ 기닀리기 μœ„ν•΄ μ»¨λ””μ…˜ λ³€μˆ˜λ₯Ό ν™œμš©ν•  수 μžˆλ‹€. μ»¨λ””μ…˜ λ³€μˆ˜λŠ” μΌμ’…μœΌ 큐 자료 κ΅¬μ‘°λ‘œμ„œ, μ–΄λ–€ μ‹€ν–‰μ˜ μƒνƒœ (λ˜λŠ” μ–΄λ–€ 쑰건) κ°€ μ›ν•˜λŠ” 것과 λ‹€λ₯Ό λ•Œ 참이 되기λ₯Ό 기닀리며 μŠ€λ ˆλ“œκ°€ λŒ€κΈ°ν•  수 μžˆλŠ” 큐이닀. λ‹€λ₯Έ μ“°λ ˆλ“œκ°€ μƒνƒœλ₯Ό λ³€κ²½μ‹œμΌ°μ„ λ•Œ, λŒ€κΈ° μ€‘μ΄λ˜ μ“°λ ˆλ“œλ₯Ό 깨우고, 계속 μ§„ν–‰ν•  수 μžˆλ„λ‘ ν•œλ‹€.

pthread_cond_t c; 라고 μ¨μ„œ cκ°€ μ»¨λ””μ…˜ λ³€μˆ˜κ°€ λ˜λ„λ‘ μ„ μ–Έν•˜κ³  μ΄ˆκΈ°ν™”ν•œλ‹€. μ»¨λ””μ…˜ λ³€μˆ˜μ—λŠ” wait() κ³Ό signal() μ΄λΌλŠ” 두 κ°€μ§€ 연산이 μ‘΄μž¬ν•œλ‹€.

wait() 은 μ“°λ ˆλ“œκ°€ 슀슀둜λ₯Ό 잠재우기 μœ„ν•΄ ν˜ΈμΆœν•˜λŠ” 것이고, signal()은 μ“°λ ˆλ“œκ°€ 무엇인가λ₯Ό λ³€κ²½ν–ˆκΈ° λ•Œλ¬Έμ— 쑰건이 참이 되기λ₯Ό 기닀리며 잠자고 있던 μ“°λ ˆλ“œλ₯Ό 깨울 λ•Œ ν˜ΈμΆœν•œλ‹€.

pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m);
pthread_cond_signal(pthread_cond_t *c);

wait()μ—μ„œ μœ μ˜ν•  점은 mutexλ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ μ‚¬μš©ν•œλ‹€λŠ” 것이닀. 호좜될 λ•Œ mutexλŠ” μž κ²¨μžˆμ—ˆλ‹€κ³  κ°€μ •ν•˜μž. wait()의 역할은 락을 ν•΄μ œν•˜κ³  ν˜ΈμΆœν•œ μ“°λ ˆλ“œλ₯Ό μž¬μš°λŠ” 것이닀. μ–΄λ–€ λ‹€λ₯Έ μ“°λ ˆλ“œκ°€ μ‹œκ·Έλ„μ„ λ³΄λ‚΄μ„œ μ“°λ ˆλ“œκ°€ κΉ¨μ–΄λ‚˜λ©΄, wait()μ—μ„œ λ¦¬ν„΄ν•˜κΈ° 전에 락을 μž¬νšλ“ν•΄μ•Ό ν•œλ‹€.

즉, 쑰건이 λ§Œμ‘±λ˜μ–΄ μž μ—μ„œ 깨어났더라고 락을 νšλ“ν•˜μ§€ λͺ»ν•˜λ©΄ λ‹€μ‹œ μž μ— λ“œλŠ” 것이닀. μ΄λ ‡κ²Œ λ³΅μž‘ν•œ μ΄μœ λŠ” μ“°λ ˆλ“œκ°€ 슀슀둜λ₯Ό 재우렀고 ν•  λ•Œ, 경쟁 쑰건의 λ°œμƒμ„ λ°©μ§€ν•˜κΈ° μœ„ν•΄μ„œμ΄λ‹€.

이해λ₯Ό 돕기 μœ„ν•΄ 예제λ₯Ό μ‚΄νŽ΄λ³΄μž.

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;
}

λΆ€λͺ¨ μ“°λ ˆλ“œκ°€ 쑰건을 검사할 λ•Œ if문이 μ•„λ‹ˆλΌ while문을 μ‚¬μš©ν•œλ‹€.


첫 번째 μΌ€μ΄μŠ€

λΆ€λͺ¨ μ“°λ ˆλ“œκ°€ μžμ‹ μ“°λ ˆλ“œλ₯Ό μƒμ„±ν•˜κ³ , 계속 μ‹€ν–‰ν•˜λ©° thr_join()을 ν˜ΈμΆœν•˜κ³ , μžμ‹ μ“°λ ˆλ“œκ°€ λλ‚˜κΈ°λ₯Ό κΈ°λ‹€λ¦¬λŠ” κ²½μš°μ΄λ‹€. 이 경우 λΆ€λͺ¨ μ“°λ ˆλ“œκ°€ 락을 νšλ“ν•˜κ³  μžμ‹μ΄ λλ‚¬λŠ”μ§€ κ²€μ‚¬ν•œ 후에 μžμ‹μ΄ λλ‚˜μ§€ μ•Šμ•˜μœΌλ―€λ‘œ wait()을 ν˜ΈμΆœν•˜μ—¬ 슀슀둜λ₯Ό 잠재우고, 락을 ν•΄μ œν•œλ‹€. μžμ‹ μ“°λ ˆλ“œκ°€ μΆ”ν›„ μ‹€ν–‰λ˜μ–΄ thr_exit()을 ν˜ΈμΆœν•˜μ—¬ λΆ€λͺ¨ μ“°λ ˆλ“œλ₯Ό κΉ¨μš΄λ‹€. ν˜ΈμΆœν–ˆλ˜ wait()μ—μ„œ 락을 νšλ“ν•œ μ±„λ‘œ λ¦¬ν„΄ν•˜μ—¬ λΆ€λͺ¨ μ“°λ ˆλ“œκ°€ μ‹€ν–‰λ˜κ³ , 락을 ν•΄μ œν•œ ν›„ μ’…λ£Œλœλ‹€.

두 번째 μΌ€μ΄μŠ€

μžμ‹ μ“°λ ˆλ“œκ°€ μƒμ„±λ˜λ©΄μ„œ μ¦‰μ‹œ μ‹€ν–‰λ˜κ³ , done 을 1둜 μ„€μ •ν•˜κ³ , 자고 μžˆλŠ” μ“°λ ˆλ“œλ₯Ό 깨우기 μœ„ν•΄ μ‹œκ·Έλ„μ„ 보낸닀. ν•˜μ§€λ§Œ 자고 μžˆλŠ” μ“°λ ˆλ“œκ°€ μ—†κΈ° λ•Œλ¬Έμ— κ·Έλƒ₯ λ¦¬ν„΄ν•œλ‹€. κ·Έ ν›„ λΆ€λͺ¨ μ“°λ ˆλ“œκ°€ μ‹€ν–‰λ˜κ³  thr_join()을 ν˜ΈμΆœν•˜κ³  done이 1μ΄λ―€λ‘œ λ°”λ‘œ λ¦¬ν„΄ν•œλ‹€.


thr_exit(), thr_join()의 μ€‘μš”μ„±μ„ 이해할 수 μžˆλ„λ‘ λͺ‡ κ°€μ§€ κ΅¬ν˜„μ˜ 방식을 μ‚΄νŽ΄λ³΄μž.

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);
}

이런 μ‹μœΌλ‘œ μž‘μ„±λœ κ²½μš°λŠ” 두 번째 μΌ€μ΄μŠ€, μžμ‹ μ“°λ ˆλ“œκ°€ μƒμ„±λœ μ¦‰μ‹œ μ‹€ν–‰λ˜μ–΄ thr_exit()을 ν˜ΈμΆœν•˜λŠ” κ²½μš°μ— μ œλŒ€λ‘œ μž‘λ™ν•˜μ§€ μ•ŠλŠ”λ‹€. μžμ‹ ν”„λ‘œμ„ΈμŠ€κ°€ μ‹œκ·Έλ„μ„ λ³΄λ‚΄μ§€λ§Œ, 깨울 μ“°λ ˆλ“œκ°€ μ—†μ–΄ λ¦¬ν„΄λœλ‹€. λΆ€λͺ¨ μ“°λ ˆλ“œλŠ” wait()을 ν˜ΈμΆœν•˜κ³  κ±°κΈ°μ„œ 멈좰있게 λœλ‹€.

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

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

이런 μ‹μœΌλ‘œ μž‘μ„±λœ 경우, 경쟁 쑰건이 λ°œμƒν•œλ‹€. λΆ€λͺ¨ μ“°λ ˆλ“œκ°€ thr_join()을 ν˜ΈμΆœν•˜κ³  λ‚˜μ„œ done이 0인 것을 ν™•μΈν•˜κ³  wait()을 ν˜ΈμΆœν•˜κΈ° 직전에 μΈν„°λŸ½νŠΈμ— κ±Έλ € μžμ‹ μ“°λ ˆλ“œκ°€ μ‹€ν–‰λ˜μ—ˆλ‹€κ³  ν•΄λ³΄μž. μžμ‹ μ“°λ ˆλ“œλŠ” done을 1둜 λ³€κ²½ν•˜κ³  μ‹œκ·Έλ„μ„ λ³΄λ‚΄μ§€λ§Œ λŒ€κΈ° 쀑인 μ“°λ ˆλ“œκ°€ μ—†λ‹€. λ‹€μ‹œ λΆ€λͺ¨ μ“°λ ˆλ“œκ°€ μ‹€ν–‰λ˜λ©΄, wait()을 ν˜ΈμΆœν•˜κ³  μž μ— λ“€μ§€λ§Œ 아무도 κΉ¨μ›Œ 쀄 수 μ—†λ‹€.

두 κ°€μ§€ κ°„λ‹¨ν•œ 예제λ₯Ό 톡해 μ»¨λ””μ…˜ λ³€μˆ˜λ₯Ό μ œλŒ€λ‘œ ν™œμš©ν•˜κΈ° μœ„ν•œ κΈ°λ³Έ μš”κ±΄μ„ μ•Œ 수 μžˆμ—ˆλ‹€. μ΄λ²ˆμ—λŠ” μ’€ 더 λ³΅μž‘ν•œ 예제λ₯Ό λ‹€λ£¨μ–΄λ³΄μž.

2. μƒμ‚°μž / μ†ŒλΉ„μž (μœ ν•œ 버퍼) 문제

λ‹€μŒμœΌλ‘œ μ‚΄νŽ΄λ³Ό 동기화 λ¬Έμ œλŠ” Dijkstraκ°€ 처음 μ œμ‹œν•œ μƒμ‚°μž/μ†ŒλΉ„μž(producer/consumer) λ¬Έμ œμ΄λ‹€. μœ ν•œ 버퍼(bounded 버퍼) λ¬Έμ œλ‘œλ„ μ•Œλ €μ Έ μžˆλ‹€. lock λŒ€μ‹  μΌλ°˜ν™”λœ μ„Έλ§ˆν¬μ–΄λ₯Ό 발λͺ…ν•˜κ²Œ 된 μ΄μœ κ°€ 이 문제 λ•Œλ¬Έμ΄λ‹€.

μ—¬λŸ¬ 개의 μƒμ‚°μž μ“°λ ˆλ“œμ™€ μ†ŒλΉ„μž μ“°λ ˆλ“œκ°€ μžˆλ‹€κ³  ν•˜μž. μƒμ‚°μžλŠ” 데이터λ₯Ό λ§Œλ“€μ–΄ 버퍼에 λ„£κ³ , μ†ŒλΉ„μžλŠ” λ²„νΌμ—μ„œ 데이터λ₯Ό κΊΌλ‚΄μ–΄ μ‚¬μš©ν•œλ‹€. μ΄λŸ¬ν•œ κ΄€κ³„λŠ” μ‹€μ œλ‘œ μ‹œμŠ€ν…œμ—μ„œ 자주 μΌμ–΄λ‚œλ‹€. 예λ₯Ό λ“€μ–΄ λ©€ν‹° μ“°λ ˆλ“œ μ›Ή μ„œλ²„μ˜ 경우 μƒμ‚°μžλŠ” HTTP μš”μ²­μ„ μž‘μ—… 큐 (μœ ν•œ 버퍼) 에 λ„£κ³ , μ†ŒλΉ„μž μ“°λ ˆλ“œλŠ” 이 νμ—μ„œ μš”μ²­μ„ κΊΌλ‚΄μ–΄ μ²˜λ¦¬ν•œλ‹€.

grep foo file.txt | wc -l와 같은 λ¬Έμž₯처럼 νŒŒμ΄ν”„ λͺ…λ ΉμœΌλ‘œ ν•œ ν”„λ‘œκ·Έλž¨μ˜ κ²°κ³Όλ₯Ό λ‹€λ₯Έ ν”„λ‘œκ·Έλž¨μ—κ²Œ 전달할 λ•Œλ„ μœ ν•œ 버퍼λ₯Ό μ‚¬μš©ν•œλ‹€. UNIX μ‰˜μ€ 좜λ ₯ κ²°κ³Όλ₯Ό UNIX νŒŒμ΄ν”„ λΌλŠ” 곳으둜 μ „μ†‘ν•œλ‹€. νŒŒμ΄ν”„μ˜ ν•œμͺ½ λμ—λŠ” wc ν”„λ‘œμ„ΈμŠ€μ˜ ν‘œμ€€ μž…λ ₯κ³Ό μ—°κ²°λ˜μ–΄ μžˆλ‹€. grep ν”„λ‘œμ„ΈμŠ€κ°€ μƒμ‚°μžκ°€ 되고 wc ν”„λ‘œμ„ΈμŠ€κ°€ μ†ŒλΉ„μžκ°€ λœλ‹€.

μœ ν•œ λ²„νΌλŠ” 곡유 μžμ›μ΄κ³ , 경쟁 쑰건의 λ°œμƒμ„ λ°©μ§€ν•˜κΈ° μœ„ν•΄ 동기화가 ν•„μš”ν•˜λ‹€. ν•œ 개의 μ •μˆ˜λ₯Ό μ‚¬μš©ν•˜κ³ , 곡유 버퍼에 값을 λ„£λŠ” ν•¨μˆ˜, 값을 κΊΌλ‚΄λŠ” ν•¨μˆ˜ 두 κ°œκ°€ μžˆλ‹€.

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;
}
cond_t cond;
mutex_t mutex;

void *producer(void *arg) {
	int i;
	for (i = 0; i < loops; 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) {
	int i;
	for (i = 0; i < loops; 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\n ”, tmp);
	}
}

μ»¨λ””μ…˜ λ³€μˆ˜ ν•˜λ‚˜μ™€ 그것과 μ—°κ²°λœ mutex 락을 μ‚¬μš©ν•˜λŠ” 방식을 λ¨Όμ € μ‹œλ„ν•΄λ³΄μž.

μƒμ‚°μžλŠ” 버퍼가 빌 λ•ŒκΉŒμ§€ κΈ°λ‹€λ¦°λ‹€. μ†ŒλΉ„μžλ„ 버퍼가 μ°¨κΈ°λ₯Ό κΈ°λ‹€λ¦°λ‹€. μƒμ‚°μžμ™€ μ†ŒλΉ„μžκ°€ 각 ν•˜λ‚˜μ”©μΈ κ²½μš°μ— μœ„μ˜ μ½”λ“œλŠ” μ •μƒμ μœΌλ‘œ λ™μž‘ν•œλ‹€. ν•˜μ§€λ§Œ μƒμ‚°μž, μ†ŒλΉ„μžκ°€ 두 개 이상씩 μ‘΄μž¬ν•˜λŠ” κ²½μš°μ—λŠ” λ¬Έμ œκ°€ μžˆλ‹€.

첫 번째 문제

λŒ€κΈ° λͺ…λ Ή μ „μ˜ if λ¬Έκ³Ό 관련이 μžˆλ‹€.

Tc1 κ³Ό Tc2 λΌλŠ” 두 개의 μ†ŒλΉ„μžκ°€ 있고 Tp λΌλŠ” μƒμ‚°μžκ°€ ν•˜λ‚˜ μžˆλ‹€κ³  κ°€μ •ν•˜μž. μ†ŒλΉ„μž (Tc1)κ°€ λ¨Όμ € μ‹€ν–‰λœλ‹€. 락 (c1) 을 νšλ“ν•˜κ³  버퍼λ₯Ό μ†ŒλΉ„ν•  수 μžˆλŠ”μ§€ κ²€μ‚¬ν•œλ‹€ (c2). 그리고 λΉ„μ–΄μžˆμŒμ„ ν™•μΈν•œ 후에 λŒ€κΈ°ν•˜λ©° (c3) 락을 ν•΄μ œν•œλ‹€. 그리고 μƒμ‚°μž (Tp)κ°€ μ‹€ν–‰λœλ‹€. 락을 νšλ“ν•˜κ³  (p1) 버퍼가 λΉ„μ—ˆλŠ”μ§€ ν™•μΈν•œλ‹€ (p2). λΉ„μ—ˆμŒμ„ λ°œκ²¬ν•˜κ³ , 버퍼λ₯Ό μ±„μš΄λ‹€ (p4). μƒμ‚°μžλŠ” 버퍼가 가득 μ°Όλ‹€λŠ” μ‹œκ·Έλ„μ„ 보낸닀 (p5). λŒ€κΈ° 쀑인 첫째 μ†ŒλΉ„μž (Tc1)λŠ” κΉ¨μ–΄λ‚˜ μ€€λΉ„ 큐 (ready queue)둜 μ΄λ™ν•œλ‹€. Tc1 은 이제 μ‹€ν–‰ν•  수 μžˆλŠ” μƒνƒœμ΄μ§€λ§Œ 아직 μ‹€ν–‰ μƒνƒœλŠ” μ•„λ‹ˆλ‹€. μƒμ‚°μžλŠ” 싀행을 κ³„μ†ν•œλ‹€. 버퍼가 μ°¨ μžˆμœΌλ―€λ‘œ λŒ€κΈ° μƒνƒœλ‘œ μ „μ΄ν•œλ‹€ (p6, p1-p3).

μ—¬κΈ°μ—μ„œ λ¬Έμ œκ°€ λ°œμƒν•œλ‹€. λ‹€λ₯Έ μ†ŒλΉ„μž (Tc2)κ°€ λΌμ–΄λ“€μ–΄μ„œ μ‹€ν–‰ν•˜λ©΄μ„œ 버퍼 값을 μ†ŒλΉ„ν•œλ‹€ (c1, c2, c4, c5, c6을 μˆ˜ν–‰, c3은 버퍼가 가득 μ°ΌκΈ° λ•Œλ¬Έμ— κ±΄λ„ˆλœ€). Tc1 이 μ‹€ν–‰λœλ‹€κ³  ν•΄λ³΄μž. λŒ€κΈ°μ—μ„œ λ¦¬ν„΄ν•˜κΈ° 전에 락을 νšλ“ν•œλ‹€. 그리고 get()을 ν˜ΈμΆœν•˜μ§€λ§Œ (c4) λ²„νΌλŠ” λΉ„μ—ˆλ‹€! μ½”λ“œλŠ” μ˜λ„ν•œ λŒ€λ‘œ κΈ°λŠ₯ν•˜μ§€ λͺ»ν–ˆλ‹€. μƒμ‚°μžκ°€ 버퍼에 λ„£μ–΄ λ‘” 값을 Tc2 κ°€ λΌμ–΄λ“€μ–΄μ„œ μ†ŒλΉ„ν•˜μ˜€κΈ° λ•Œλ¬Έμ— Tc1 이 λΉ„μ–΄ μžˆλŠ” 버퍼λ₯Ό μ½λŠ” ν–‰μœ„λ₯Ό λ§‰μ•˜μ–΄μ•Ό ν–ˆλ‹€.

문제의 원인은 Tc1이 κΉ¨μ–΄λ‚˜μ„œ μ‹€ν–‰λ˜κΈ°κΉŒμ§€μ˜ 사이에 μœ ν•œ λ²„νΌμ˜ μƒνƒœκ°€ λ³€κ²½λ˜μ—ˆκΈ° λ•Œλ¬Έμ΄λ‹€. μ‹œκ·Έλ„μ€ μ“°λ ˆλ“œλ₯Ό 깨우기만 ν•˜κ³ , κΉ¨μ–΄λ‚œ μ“°λ ˆλ“œκ°€ μ‹€μ œ μ‹±ν–‰λ˜λŠ” μ‹œμ μ— κ·Έ μƒνƒœκ°€ μœ μ§€λœλ‹€λŠ” 보μž₯은 μ—†λ‹€. 이런 μ‹μœΌλ‘œ μ‹œκ·Έλ„μ„ μ •μ˜ν•˜λŠ” 것을 Mesa Semantic이라 ν•œλ‹€. λŒ€λΉ„λ˜λŠ” κ°œλ…μ€ Hoare Semantic인데 κ΅¬ν˜„ν•˜κΈ°λŠ” 더 μ–΄λ ΅μ§€λ§Œ κΉ¨μ–΄λ‚œ μ¦‰μ‹œ μ“°λ ˆλ“œκ°€ μ‹€ν–‰λ˜λŠ” 것을 보μž₯ν•œλ‹€.

ν•΄κ²° 방법: if -> while

if 문을 while 문으둜 λ³€κ²½ν•˜λ©΄ 이 문제λ₯Ό ν•΄κ²°ν•  수 μžˆλ‹€.

cond_t cond;
mutex_t mutex;

void *producer(void *arg) {
	int i;
	for (i = 0; i < loops; 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) {
	int i;
	for (i = 0; i < loops; 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\n ”, tmp);
	}
}

μ†ŒλΉ„μž Tc1 이 κΉ¨μ–΄λ‚˜μ„œ (락을 νšλ“ν•œ μƒνƒœ), μ¦‰μ‹œ 곡유 λ³€μˆ˜μ˜ μƒνƒœλ₯Ό μž¬ν™•μΈν•œλ‹€ (c2). λ§Œμ•½ 이 μ‹œμ μ— 버퍼가 λΉ„μ–΄ μžˆλ‹€λ©΄, μ†ŒλΉ„μžλŠ” λŒ€κΈ° μƒνƒœλ‘œ λŒμ•„κ°„λ‹€ (c3). λ¬Έμ œκ°€ ν•΄κ²°λ˜μ—ˆλ‹€. Mesa semantic의 μ»¨λ””μ…˜ λ³€μˆ˜μ—μ„œ κ°€μž₯ 기본적인 법칙은 μ–Έμ œλ‚˜ while 문을 μ‚¬μš©ν•˜λΌλŠ” 것이닀.

두 번째 문제

이 λ¬Έμ œλŠ” μ†ŒλΉ„μž μ“°λ ˆλ“œ Tc1 κ³Ό Tc2 κ°€ λ¨Όμ € μ‹€ν–‰ν•œ 후에 λ‘˜ λ‹€ λŒ€κΈ° μƒνƒœμ— μžˆμ„ λ•Œ λ°œμƒν•œλ‹€ (c3).

μƒμ‚°μžκ°€ μ‹€ν–‰λ˜μ–΄ 버퍼에 값을 λ„£κ³  λŒ€κΈ° 쀑인 μ“°λ ˆλ“œ ν•˜λ‚˜λ₯Ό 깨우고 (Tc1 을 κΉ¨μ› λ‹€κ³  ν•˜μž), μžμ‹ μ€ λŒ€κΈ°ν•œλ‹€. 이제 ν•˜λ‚˜μ˜ μ†ŒλΉ„μž (Tc1)κ°€ μ‹€ν–‰ν•  μ€€λΉ„κ°€ λ˜μ—ˆκ³  쑰건에 μ˜ν•΄ Tc2 와 Tp λŠ” λŒ€κΈ° 쀑이닀. 이제 λ¬Έμ œκ°€ λ°œμƒν•˜λ„λ‘ λ§Œλ“€ 것이닀.

μ†ŒλΉ„μž Tc1이 wait()μ—μ„œ 리턴을 λ°›μ•„ κΉ¨μ–΄λ‚˜κ³  (c3) 쑰건을 μž¬ν™•μΈν•œλ‹€ (c2). 버퍼가 μ°¨μžˆλ‹€λŠ” 것을 λ°œκ²¬ν•˜κ³  값을 μ†ŒλΉ„ν•œλ‹€ (c4). 이 μ†ŒλΉ„μžλŠ” μ‹œκ·Έλ„μ„ μ „μ†‘ν•˜μ—¬ (c5) λŒ€κΈ°μ€‘μΈ μ“°λ ˆλ“œ 쀑 ν•˜λ‚˜λ₯Ό κΉ¨μš΄λ‹€. μ΄λ•Œ μ–΄λ–€ μ“°λ ˆλ“œλ₯Ό 깨울 것인가?

μ†ŒλΉ„μžκ°€ 버퍼λ₯Ό λΉ„μ› κΈ° λ•Œλ¬Έμ— μƒμ‚°μžλ₯Ό λ‹Ήμ—°νžˆ κΉ¨μ›Œμ•Ό ν•œλ‹€. ν•˜μ§€λ§Œ, λ§Œμ•½ μ†ŒλΉ„μž Tc2 λ₯Ό κΉ¨μš΄λ‹€λ©΄ (λŒ€κΈ° 큐가 μ–΄λ–»κ²Œ κ΄€λ¦¬λ˜λŠλƒμ— 따라 λ‹Ήμ—°νžˆ λ°œμƒν•  수 μžˆλ‹€), λ¬Έμ œκ°€ λ°œμƒν•œλ‹€. μ†ŒλΉ„μž Tc2 κ°€ κΉ¨μ–΄λ‚˜λ©΄ 버퍼가 λΉ„μ–΄ μžˆλ‹€λŠ” 것을 λ°œκ²¬ν•œ 후에 (c2) λ‹€μ‹œ λŒ€κΈ° μƒνƒœλ‘œ λ“€μ–΄κ°„λ‹€ (c3). 버퍼에 값을 λ„£μ–΄μ•Ό ν•˜λŠ” μƒμ‚°μž Tp λŠ” λŒ€κΈ° 쀑이닀. λ‹€λ₯Έ μ†ŒλΉ„μž μ“°λ ˆλ“œ Tc1 μ—­μ‹œ λŒ€κΈ° μƒνƒœμ— λ“€μ–΄κ°„λ‹€. μ„Έ 개의 μ“°λ ˆλ“œκ°€ λͺ¨λ‘ λŒ€κΈ° μƒνƒœλ‹€.

μ‹œκ·Έλ„μ„ λ³΄λ‚΄λŠ” 것은 κΌ­ ν•„μš”ν•˜μ§€λ§Œ λŒ€μƒμ΄ λͺ…ν™•ν•΄μ•Ό ν•œλ‹€. μ†ŒλΉ„μžλŠ” λ‹€λ₯Έ μ†ŒλΉ„μžλ₯Ό 깨울 수 μ—†κ³  μƒμ‚°μžλ§Œ κΉ¨μ›Œμ•Ό ν•˜λ©°, λ°˜λŒ€λ‘œ μƒμ‚°μžμ˜ κ²½μš°λ„ λ§ˆμ°¬κ°€μ§€λ‹€

단일 버퍼 μƒμ‚°μž/μ†ŒλΉ„μž 해법

두 개의 μ»¨λ””μ…˜ λ³€μˆ˜λ₯Ό μ‚¬μš©ν•˜μ—¬ μ‹œμŠ€ν…œμ˜ μƒνƒœκ°€ λ³€κ²½λ˜μ—ˆμ„ λ•Œ κΉ¨μ›Œμ•Ό ν•˜λŠ” μ“°λ ˆλ“œμ—κ²Œλ§Œ μ‹œκ·Έλ„μ„ μ œλŒ€λ‘œ μ „λ‹¬ν•œλ‹€.

cond_t empty, fill;
mutex_t mutex;

void *producer(void *arg) {
	int i;
	for (i = 0; i < loops; 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) {
	int i;
	for (i = 0; i < loops; 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\n ”, tmp);
	}
}

μ΅œμ’…μ μΈ μƒμ‚°μž/μ†ŒλΉ„μž 해법

λ§ˆμ§€λ§‰ 변경을 톡해 병행성을 μ¦κ°€μ‹œν‚€κ³  효율적으둜 λ§Œλ“€μ–΄ 보자. 버퍼 곡간을 μΆ”κ°€ν•˜μ—¬ λŒ€κΈ° μƒνƒœμ— λ“€μ–΄κ°€κΈ° 전에 μ—¬λŸ¬ 값듀이 생산될 수 μžˆλ„λ‘ ν•˜λŠ” 것, 그리고 λ§ˆμ°¬κ°€μ§€λ‘œ μ—¬λŸ¬ 개의 값이 λŒ€κΈ° μƒνƒœ 전에 생산될 수 μžˆλ„λ‘ ν•˜λŠ” 것이닀.

μš°μ„  λ‹€μŒκ³Ό 같이 버퍼 ꡬ쑰와 put(), get() ν•¨μˆ˜λ₯Ό λ³€κ²½ν•˜μ˜€λ‹€.

int buffer[MAX];
int fill = 0;
int use = 0;
int count = 0;

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

int get() {
	int tmp = buffer[use];
	use = (use + 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); // 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;
	for (i = 0; i < loops; 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\n ”, tmp);
	}
}

μƒμ‚°μžλŠ” λͺ¨λ“  버퍼가 ν˜„μž¬ 가득 μ°¨ μžˆλ‹€λ©΄ λŒ€κΈ° μƒνƒœμ— λ“€μ–΄κ°€κ³ , μ†ŒλΉ„μžλ„ λͺ¨λ“  버퍼가 λΉ„μ–΄ μžˆλ‹€λ©΄ λŒ€κΈ°μ— λ“€μ–΄κ°„λ‹€.

3. μ»¨λ””μ…˜ λ³€μˆ˜ μ‚¬μš© μ‹œ 주의점

팁 : 쑰건에 while 문을 μ‚¬μš©ν•˜μž (if 문은 μ•„λ‹ˆλ‹€) λ©€ν‹° μ“°λ ˆλ“œ ν”„λ‘œκ·Έλž¨μ—μ„œ 쑰건을 검사할 λ•Œμ—λŠ” 항상 while 문을 μ‚¬μš©ν•˜λŠ” 것이 μ˜³λ‹€. μ‹œκ·Έλ„ μ „λ‹¬μ˜ μ˜λ―Έμ— 따라 if 문을 μ‚¬μš©ν•˜λŠ” 것은 λ§žμ„ μˆ˜λ„ μžˆμ„ 뿐이닀. 그러 λ―€λ‘œ 항상 while 문을 μ‚¬μš©ν•˜μž, 그러면 μž‘μ„±ν•œ μ½”λ“œκ°€ μ˜λ„ν•œ λŒ€λ‘œ λ™μž‘ν•  것이닀. 쑰건 검사에 while 문을 μ‚¬μš©ν•˜λŠ” 것은 κ±°μ§“μœΌλ‘œ 깨운 경우 (spurious wakeup) 에 λŒ€μ²˜ν•  수 μžˆλ„λ‘ ν•΄ μ€€λ‹€. μ–΄λ–€ μ“°λ ˆλ“œ νŒ¨ν‚€μ§€λŠ” κ΅¬ν˜„μƒμ˜ 문제둜 ν•˜λ‚˜μ˜ μ‹œκ·Έλ„μ— μ˜ν•΄μ„œ 두 개의 μ“°λ ˆλ“œκ°€ κΉ¨μ–΄λ‚˜λŠ” κ²½μš°λ„ κ°€λŠ₯ν•˜λ‹€. μ“°λ ˆλ“œκ°€ 쑰건을 μž¬κ²€μ‚¬ν•΄μ•Ό ν•˜λŠ” μ΄μœ λŠ” κ±°μ§“μœΌλ‘œ 깨운 κ²½μš°κ°€ 있기 λ•Œλ¬Έμ΄λ‹€.

// λͺ‡ byteλ‚˜ νž™μ΄ λΉ„μ—ˆλŠ”κ°€?
int bytesLeft = MAX_HEAP_SIZE;
cond_t c;
mutex_t m;

void *allocate(int size) {
	Pthread_mutex_lock(&m);
	while (bytesLeft < size)
		Pthread_cond_wait(&c, &m);
	void *ptr = . . . ; // νž™μ—μ„œ λ©”λͺ¨λ¦¬λ₯Ό ν• λ‹Ή λ°›μŒ
	bytesLeft βˆ’= size;
	Pthread_mutex_unlock(&m);
	return ptr;
}

void free(void *ptr, int size) {
	Pthread_mutex_lock(&m);
	bytesLeft += size;
	Pthread_cond_signal(&c); // μ‹œκ·Έλ„ 전달 λŒ€μƒμ€?..
	Pthread_mutex_unlock(&m);
}

λ©€ν‹° μ“°λ ˆλ“œ 기반 λ―Έλͺ¨λ¦¬ ν• λ‹Ή 라이브러리 μ˜ˆμ œμ΄λ‹€. λ©”λͺ¨λ¦¬ ν• λ‹Ή μ½”λ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄, 곡간이 생길 λ•ŒκΉŒμ§€ κΈ°λ‹€λ €μ•Ό ν•  수 μžˆλ‹€. 또 μ“°λ ˆλ“œκ°€ λ©”λͺ¨λ¦¬ λ°˜λ‚©μ‹œ μ‚¬μš© κ°€λŠ₯ν•œ λ©”λͺ¨λ¦¬ κ³΅κ°„μ˜ λ°œμƒμ„ μ•Œλ¦¬λŠ” μ‹œκ·Έλ„μ„ 생성할 수 μžˆλ‹€. ν•˜μ§€λ§Œ 이 μ½”λ“œμ—λŠ” λ¬Έμ œκ°€ μžˆλ‹€ μ–΄λ–€ μ“°λ ˆλ“œκ°€ κΉ¨μ–΄λ‚˜μ•Ό ν• κΉŒ?

μ“°λ ˆλ“œ TaλŠ” 100을 ν• λ‹Ήλ°›κΈΈ μ›ν•˜κ³ , μ“°λ ˆλ“œ TbλŠ” 10을 ν• λ‹Ήλ°›κΈΈ μ›ν•˜λŠ” μƒνƒœμ—μ„œ, μ–΄λ–€ μ“°λ ˆλ“œκ°€ 50만큼 λ©”λͺ¨λ¦¬λ₯Ό λ°˜ν™˜ν•œ 경우, Taκ°€ κΉ¨μ–΄λ‚˜λ©΄ μ•ˆ 되고 Tbκ°€ κΉ¨μ–΄λ‚˜μ•Ό ν•œλ‹€. 이런 λ¬Έμ œλŠ” 두 개의 μ»¨λ””μ…˜ λ³€μˆ˜λ₯Ό μ‚¬μš©ν•΄λ„ ν•΄κ²°ν•  수 μ—†λ‹€.

Lampsonκ³Ό Redell이 μ œμ‹œν•œ 해법은 λ‹¨μˆœν•˜λ‹€. pthread_cond_signal()을 λŒ€κΈ° 쀑인 λͺ¨λ“  μ“°λ ˆλ“œλ₯Ό κΉ¨μš°λŠ” pthread_cond_broadcast()둜 λ°”κΏ”μ„œ μ‚¬μš©ν•˜λ©΄ λœλ‹€. κ·Έλ ‡κ²Œ ν•¨μœΌλ‘œμ¨ κΉ¨μ–΄λ‚˜μ•Ό ν•  μ“°λ ˆλ“œκ°€ μžˆλ‹€λ©΄ κΉ¨μ–΄λ‚  수 μžˆλ„λ‘ ν•œλ‹€. κ·Έλ ‡κ²Œ κΉ¨μ–΄λ‚œ μ“°λ ˆλ“œλ“€μ€ κΉ¨μ–΄λ‚˜μ„œ 쑰건을 μž¬κ²€μ‚¬ν•˜κ³ , μ¦‰μ‹œ λŒ€κΈ° μƒνƒœλ‘œ λ‹€μ‹œ λ“€μ–΄κ°„λ‹€.

Lampsonκ³Ό Redell은 이런 경우λ₯Ό 포함 쑰건(covering condition)이라고 ν–ˆλ‹€. μ™œλƒν•˜λ©΄ (보수적으둜) μ“°λ ˆλ“œκ°€ κΉ¨μ–΄λ‚˜μ•Ό ν•˜λŠ” λͺ¨λ“  경우λ₯Ό λ‹€ ν¬ν•¨ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€. λΆˆν•„μš”ν•˜κ²Œ λ§Žμ€ μ“°λ ˆλ“œκ°€ κΉ¨μ–΄λ‚˜λŠ” 단점이 μžˆλ‹€. λ¬Έλ§₯ μ „ν™˜ μ˜€λ²„ν—€λ“œκ°€ 크닀. μ˜ˆλ¦¬ν•œ λ…μžλΌλ©΄ 이 방법을 μ•žμ—μ„œ μ‚¬μš©ν–ˆμ„ μˆ˜λ„ μžˆλ‹€λŠ” 것을 μ•Œ 것이닀 (μ»¨λ””μ…˜ λ³€μˆ˜λ₯Ό ν•˜λ‚˜λ§Œ μ‚¬μš©ν•˜λŠ” μƒμ‚°μž/μ†ŒλΉ„μž 문제λ₯Ό 보자). ν•˜μ§€λ§Œ κ·Έ κ²½μš°μ—λŠ” 더 쒋은 해법이 μžˆμ—ˆκΈ° λ•Œλ¬Έμ— κ·Έ 방법을 νƒν–ˆα¨©λ‹€. 일반적으둜 μ‹œκ·Έλ„μ„ λΈŒλ‘œλ“œμΊμŠ€νŠΈ (broadcast)둜 바꿨을 λ•Œλ§Œ ν”„λ‘œκ·Έλž¨μ΄ λ™μž‘ν•œλ‹€λ©΄ μ•„λ§ˆλ„ 버그가 μ‘΄μž¬ν•˜λŠ” 것일 κ±°λ‹€. μ•žμ„œ 닀룬 λ©”λͺ¨λ¦¬ ν• λ‹Ή 문제의 경우 λΈŒλ‘œλ“œμΊμŠ€νŠΈλ₯Ό μ μš©ν•˜λŠ” 것이 κ°€μž₯ 자λͺ…ν•œ 해법이닀.