wait()와 notify()

글 : 윤경구

wait()와 notify()는 동일한 잠금 객체 하에서 동기화되어 실행되는 블록에서 실행될 수 있다.
notify() 이후의 실행 순서는 먼저 notify()를 호출한 쓰레드가 synchronized 블록을 벗어난 다음에 wait()하다 깨어난 쓰레드의 수행을 보장하는 순서로 된다.

참고: Mesa 방식과 Hoare 방식
Mesa 방식에서는 wait()하던 쓰레드가 다시 잠금을 획득하는 것은 순전히 wait()하던 객체의 책임이다.
즉, notify()하던 쓰레드는 wait()하는 객체를 잠금을 획득하려고 하는(스케쥴되려고 하는) 쓰레드 목록에 올려줄 뿐, 자신의 임계영역(critical section)을 계속 수행한 다음 임계 영역을 벗어나면서 잠금을 푼다.

Hoare 방식에서는 notify()하던 쓰레드가 잠시 잠금을 풀고 제어를 깨어날 쓰레드에게 넘겨준다. wait()하던 객체가 임계영역 수행을 마친 후 다시 notify()했던 쓰레드에게 잠금에 대한 제어를 돌려준다.

Mesa 방식을 사용하게 되면 깨어난 쓰레드가 실행되기 전에 어떤 다른 쓰레드가 잠금을 획득하여 수행되면서 데이터를 변경시킬 수 있는 단점이 있다. 그러나 Hoare 방식에 비해 구현이 훨씬 쉽다는 장점이 있다.

자바의 구현은 Mesa 방식이다. 각 잠금 객체에는 기다리는 쓰레드 목록(wait set)이 있어서 notify()를 하면 wait set에 있는 쓰레드들 중 임의의 하나를 wait set에서 지워서 다시 스케쥴링될 수 있도록 한다. 물론 notify()한 쓰레드가 임계영역을 벗어나 잠금을 풀지 않는 한 스케쥴링된 쓰레드가 잠금을 획득할 수는 없다.

쓰레드가 wait()에 도달하면 잠금 객체를 풀고 notify()되기를 기다리다가 notify()로 인해 깨어난 다음에 자동으로 다시 잠금 객체를 잠그고 나머지 synchronized 블록 내부를 수행하게 된다.

유닉스 POSIX 쓰레드 프로그래밍을 할 때의 pthread_cond_signal()과 pthread_cond_wait() 함수가 각각 자바의 notify(), wait()에 해당한다고 볼 수 있다.

WaitTest.java

/*
 * WaitTest.java
 * Copyright 1998 yoonforh@yahoo.com
 */
class WaitTest {
	int value;

	class Putter extends Thread {
	Object locker;
		public Putter(Object locker) {
			this.locker=locker;
		}
		public void run() {
			doPut(locker);
		}
	}

	void doPut(Object obj) {
		synchronized (obj) {
			value++;
			System.out.println("PUTTER["+Thread.currentThread().getName()+"]:value="+value);
			notify();
			System.out.println("PUTTER["+Thread.currentThread().getName()+"]:after notify");
		}
	}
	class Getter extends Thread {
	Object locker;
		public Getter(Object locker) {
			this.locker=locker;
		}
		public void run() {
			doGet(locker);
		}
	}

	void doGet(Object obj) {
		synchronized (obj) {
			System.out.println("GETTER["+Thread.currentThread().getName()+"]:before wait");
			try {
				wait();
			} catch (InterruptedException ie) {
				System.out.println("Interrupted...");
			}
			value--;
			System.out.println("GETTER["+Thread.currentThread().getName()+"]:value="+value);
		}
	}
	public WaitTest() {
		Thread one = new Putter(this);
		Thread two = new Putter(this);
		Thread three = new Putter(this);
		Thread four = new Putter(this);
		Thread five = new Putter(this);
		Thread six = new Getter(this);
		Thread seven = new Getter(this);
		Thread eight = new Getter(this);
		Thread nine = new Getter(this);
		Thread ten = new Getter(this);
		six.start();
		seven.start();
		eight.start();
		nine.start();
		ten.start();
		one.start();
		two.start();
		three.start();
		four.start();
		five.start();
	}

	public static void main(String args[]) {
		new WaitTest();
	}

}
실행 결과 예 1(Digital Unix 4.0B(1CPU) JDK 1.1.6에서 실행)
GETTER[Thread-7]:before wait
GETTER[Thread-8]:before wait
GETTER[Thread-9]:before wait
GETTER[Thread-10]:before wait
GETTER[Thread-11]:before wait
PUTTER[Thread-2]:value=1
PUTTER[Thread-2]:after notify
GETTER[Thread-7]:value=0
PUTTER[Thread-3]:value=1
PUTTER[Thread-3]:after notify
PUTTER[Thread-4]:value=2
PUTTER[Thread-4]:after notify
GETTER[Thread-8]:value=1
PUTTER[Thread-5]:value=2
PUTTER[Thread-5]:after notify
GETTER[Thread-9]:value=1
PUTTER[Thread-6]:value=2
PUTTER[Thread-6]:after notify
GETTER[Thread-10]:value=1
GETTER[Thread-11]:value=0
실행 결과 예 2(Digital Unix 4.0B(1CPU) JDK 1.1.6에서 실행)
이 경우에는 notify가 wait에 앞서 연속 발생하여 무시되었기 때문에 몇몇 wait는 무한히 기다리게 된다.(데드락)
GETTER[Thread-7]:before wait
PUTTER[Thread-2]:value=1
PUTTER[Thread-2]:after notify
PUTTER[Thread-3]:value=2
PUTTER[Thread-3]:after notify <- wait하고 있는 쓰레드가 없는데 notify
PUTTER[Thread-4]:value=3
PUTTER[Thread-4]:after notify <- wait하고 있는 쓰레드가 없는데 notify
GETTER[Thread-7]:value=2
GETTER[Thread-8]:before wait
PUTTER[Thread-5]:value=3
PUTTER[Thread-5]:after notify
GETTER[Thread-9]:before wait
PUTTER[Thread-6]:value=4
PUTTER[Thread-6]:after notify
GETTER[Thread-10]:before wait <- 이 쓰레드는 무한히 기다리게 된다.
GETTER[Thread-8]:value=3
GETTER[Thread-11]:before wait <- 이 쓰레드는 무한히 기다리게 된다.
GETTER[Thread-9]:value=2

condtest.c(유닉스의 pthread 사용)

이 소스 코드는 POSIX.1c standard(draft 10) 버전에 따른 것이므로 다른 버전의 경우에는 조금의 수정이 필요하다.
/*
 * condtest.c
 * Copyright 1998 yoonforh@yahoo.com
 */
#include <pthread.h>
#include <stdio.h>

/******************************** DEFINES *****************************/
#define	NUMTHREADS	10
#define	NUM_PUTTERS	5
#define	NUM_GETTERS	5

pthread_mutex_t mutex;
pthread_cond_t cond;
int value=0;

main(int argc, char **argv)
{
	int	numThr;
	pthread_t threadId[NUMTHREADS];
	pthread_attr_t	threadAttr;
	void	*Getters();
	void	*Putters();
	int		i;
	int		*status;

	pthread_mutexattr_t *m_attr;
	pthread_condattr_t *c_attr;

	m_attr=(pthread_mutexattr_t *) malloc(sizeof(pthread_mutexattr_t));
	c_attr=(pthread_condattr_t *) malloc(sizeof(pthread_condattr_t));
	pthread_mutexattr_init(m_attr);
	pthread_condattr_init(c_attr);

	pthread_mutex_init(&mutex , m_attr);
	pthread_cond_init(&cond , c_attr);

	numThr = NUM_PUTTERS + NUM_GETTERS;
	if (numThr > NUMTHREADS)
		exit(0);

	pthread_attr_init(&threadAttr);

	for (i = 0; i < NUM_PUTTERS; ++i)
		pthread_create(&threadId[i], &threadAttr, Getters, NULL);

	for (i= NUM_PUTTERS ; i < numThr; ++i)
		pthread_create(&threadId[i], &threadAttr, Putters, NULL);

	for (i = 0; i < numThr; ++i)
		pthread_join(threadId[i],(void**) &status);

	exit(0);
}

void *
Putters(void *pThrArg)
{
	pthread_t	Tid;
	int	i;
	char msg[256];

	Tid = pthread_self();

    pthread_mutex_lock(&mutex);

	value++;
	fprintf(stderr, "PUTTER[%d]:value=%d\n", Tid, value);
    pthread_cond_signal(&cond);
	fprintf(stderr, "PUTTER[%d]:after cond_signal\n", Tid);

    pthread_mutex_unlock(&mutex);
	return(NULL);
}

void *
Getters(void *pThrArg)
{
	volatile int	 i;
	int	 	nmsgs;
	int		Rc;
	char	msg[256];
	pthread_t	Tid;

	Tid = pthread_self();

    pthread_mutex_lock(&mutex);

	fprintf(stderr, "GETTER[%d]:before cond_wait\n", Tid);
    pthread_cond_wait(&cond, &mutex);
	value--;
	fprintf(stderr, "GETTER[%d]:value=%d\n", Tid, value);

    pthread_mutex_unlock(&mutex);
	return(NULL);
}
실행 결과 예(Digital Unix 4.0B(1CPU)에서 실행)
GETTER[1073797136]:before cond_wait
GETTER[1073840176]:before cond_wait
GETTER[1073841552]:before cond_wait
GETTER[1073842928]:before cond_wait
GETTER[1073844304]:before cond_wait
PUTTER[1073845680]:value=1
PUTTER[1073845680]:after cond_signal
PUTTER[1073889328]:value=2
PUTTER[1073889328]:after cond_signal
PUTTER[1073890704]:value=3
PUTTER[1073890704]:after cond_signal
PUTTER[1073892080]:value=4
PUTTER[1073892080]:after cond_signal
PUTTER[1073893456]:value=5
PUTTER[1073893456]:after cond_signal
GETTER[1073797136]:value=4
GETTER[1073840176]:value=3
GETTER[1073841552]:value=2
GETTER[1073842928]:value=1
GETTER[1073844304]:value=0

condtest.c(Win32)

[PENDING]


1998년 10월 27일에 처음 만들어지고, 1999년 8월 24일에 최종 변경되었습니다.

튜토리얼 페이지로 돌아가기
Last modified: Tue Aug 24 13:55:54 1999