Spurious Wakeup in Java

Tags:

http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Object.html#wait(long)

자바에서 Object.wait( ) 를 호출할때는 다음과 같이 해야한다는 것은 상식입니다.

synchronized (obj) {
    while (<condition does not hold>)
        obj.wait(timeout);
    ... // Perform action appropriate to condition
}

문제는 임의의 쓰레드를 Object.notify 로 깨울때도 위의 코드처럼 while이 필요한가 입니다. 그걸 논의 하기에 앞서 먼저 notify를 부를까 아니면 notifyAll을 부를까의 문제가 있습니다. 하나는 단일 쓰레드만 깨우고 하나는 모든 wait 중인 쓰레드를 깨웁니다. notify를 하면 하나의 쓰레드만 깨워지지만 어떤 쓰레드를 깨울까는 알 수 없습니다. wait중에 notify 받은 쓰레드는 깨나지만 notify한 측이 synchronized블럭을 빠져나올때까지 실행될 기회를 얻지못하며, notify한 측이 블럭을 빠져나왔다 하더라도 그 다음 순서가 반드시 notify 받은 쓰레드일 필요는 없습니다.

어쨌든, notify를하게 될 경우의 문제는 다수의 쓰레드가 하나의 객체에 synchornized 를 걸고 있을 때, 그들 중 한녀석만 notify를 받게 될 우려가 있다는 것입니다. 한마디로 stravation 되는 녀석이 발생할 수 있죠. 그런 이유로 notifyAll을 하면 starvation을 대충 (완전히는 아니죠) 막을 수 있고, 이상한 쓰레드 하나가 자원을 모두 먹는 일을 막습니다.

다시 원래의 문제로 돌아와서, notifyAll이라면 모든 쓰레드가 한꺼번에 깨나므로 걔네들은 condition 검사가 while이 아니라 if로 되고 있다면 모두 “Perform action appropriate..” 부분을 수행하여 논리적 오류가 발생하게 됩니다. 이걸 막으려면 while문으로 조건을 주어야 딱 한녀석만 조건을 만족하면서 작업을 수행하게 되죠.

그러나 notify할때는 단 하나의 쓰레드만 깨날 것이므로 이런 while 문이 필요 없을거라고 생각할 수 있으나 이는 틀린 정보입니다.

A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied.

이렇게 단 하나의 쓰레드만 깨울 수 없는 이유는 POSIX 쓰레드의 근본적인 원리와 맞닿아 있는 듯 합니다. 관련된 설명은 Doug Lea의 책과 Joshua Bloch 의 책에 나와있다는데, 일단 후자인 Effective Java에서는 원리에 대해서 나와있지는 않습니다. (POSIX 표준에 대한 링크만 있음) 누군가 아시면 설명해주시면 감사 ㅎㅎ 아무도 안알려주시면 제가 알아보죠 뭐;;;

Comments

3 responses to “Spurious Wakeup in Java”

  1. abraxsus Avatar

    spurious interrupt라는게 있다-_-;
    위와 같은 똑같은 코드가 커널내에서도 쓰인다.

    while(!condition)
    wakeup(process);

    이렇게해서 spurious interrupt로부터 방어를 하는데, 즉 짜가 interrupt라는거지..
    이건 아마도 8259A PIC칩이 도무지 원인을 알수없는 상황에 닥쳤을때 내서는 안될 인터럽트를
    내는것때문에 일어나는것 같구나. 구글링해보니 뭐 어쩌다가 line에 voltage가 threshold를
    넘긴다거나 별의별 쓸데없는 전기적인 이유들때문에 인터럽트들이 발생하는데 이런것들을
    spurious interrupt라고 한다. 아래 참조…

    http://mailman.linuxchix.org/pipermail/techtalk/2002-August/012697.html

    이 XT에서나 쓰이던 구닥다리 8259A칩때문인것인지 아니면 피할수없는 low level에서의
    이유들때문인지(아마 후자일듯) 암튼 우리가 알아야하는것은 interrupt가 왔다고 해서
    해당 event가 반드시 일어난것은 아니라는 웃기는 상황이지.. 따라서 while로 한번 싸서 보호해
    준다.. 웃기지?? ㅋ…

  2. abraxsus Avatar

    Java의 threading을 정확히는 모르겠지만,
    spurious wakeup이 아마도 spurious interrupt와 같은 상황때문에 일어나는 wakeup이라면
    while은 비단 notifyAll때문만이 아니라 이런 spurious wakeup을 방어하기 위한것이라는
    생각이 드네. 그래도 여러가지 의문점이 계속 제기되지만,…
    자세한것은 공부해봐야겠는걸. 너가 얘기하는 Java thread들의 starvation등은 잘 이해가
    안가고있고, 머리가 이해하려고 하질 않고있도다…-_-;;;;

  3. MKSeo Avatar
    MKSeo

    음, 이건 POSIX표준에 대한 이야기니, 커널 내 인터럽트와는 약간 다른내용이었지만 아무튼 비슷한 것인듯. c/c++/java/c#/ruby/python 모두 쓰레딩은 어차피 POSIX 표준에 근거하므로 언어간 차이는 없다고 본다. 좀 편한 언어, 더 빠른 구현은 있지만.

    POSIX 쓰레딩에서의 spurious wakeup 의 존재 이유에 대해서는, 평소 신뢰해 마지 않는 Dave Butenhof 의 글을 comp.lang.threads에서 퍼왔음.

    “A. Meijster” wrote:
    > I read several times in the newsgroup about people
    > stating things on ‘spurious wakeups’. What do you mean
    > by that? Does it mean a thread got woken up, while there
    > was no signal/broadcast? In that case, it seems
    > to me very hard to write correct threaded programs in the
    > first place. See, e.g. the discussion we had on barrier
    > implementations. It is now completely unclear to
    > me which implementation is correct/wrong.

    Spurious wakeups are two things:

    * Write your program carefully, and make sure it works even if you
    missed something.
    * Support efficient SMP implementations

    There may be rare cases where an “absolutely, paranoiacally correct”
    implementation of condition wakeup, given simultaneous wait and
    signal/broadcast on different processors, would require additional
    synchronization that would slow down ALL condition variable operations
    while providing no benefit in 99.99999% of all calls. Is it worth the
    overhead? No way!

    But, really, that’s an excuse because we wanted to force people to write
    safe code. (Yes, that’s the truth.)

    What if you’ve got two threads servicing a work queue. A thread queues
    an item, and signals the condition variable to awaken a server thread.
    Except only one of the two server threads is waiting. The other, having
    just completed a work item, swings back around to look for a new one.
    Gee, there’s a work item just waiting for it. How convenient!

    Now… does it make sense for this running, “cache hot” thread to go to
    sleep waiting for the NEXT work item, or to simply take the one that’s
    waiting and go with it? The one you awakened may or may not immediately
    find a processor, and will take a bit of time to start up. Forcing the
    application to wait for it would be counterproductive. So, yeah, the one
    already running should take it. OK, so now the awakened thread spins up,
    pulls an item from the queue, and the program dies with a SIGSEGV
    because it’s empty. What’s wrong? The problem is that the thread wasn’t
    “defensively codedd” to check the bloody queue before removing an item.
    If it had checked, it’d have seen that the queue was empty, and then it
    could simply go back to sleep until the next item.

    THAT’s a spurious wakeup, too. (Alternately termed a “stolen wakeup”;
    but the distinction is irrelevant to the call site.) Spurious wakeups
    are good, but for implementation and application performance. And the
    solution is trivial; and should be immediately obvious to anyone who’s
    ever taken a basic programming class. Don’t ASSuME… CHECK!

    POSIX says you cannot write:

    pthread_mutex_lock(&m);
    pthread_cond_wait(&c,&m);
    pthread_mutex_unlock(&m);

    That doesn’t tell future maintainers why you’re waiting. It doesn’t
    protect against stolen or spurious wakes. Instead, you write:

    pthread_mutex_lock(&m);
    while (!predicate)
    pthread_cond_wait(&c,&m);
    pthread_mutex_unlock(&m);

    Now anyone looking at your code knows why you’re waiting. And if you, or
    the implementation, “screwed up”, the situation is self correcting.

    Yes, this can be a performance sink if it’s done too much. You should
    consider implementation spurious wakeups to be extremely rare events,
    though. And if you get enough stolen wakes to be significant, then
    probably either your “work units” are too small to keep the servers
    busy, or you’ve got too many servers contending for the same queue.

    /——————[ David.Buten…@compaq.com ]——————\
    | Compaq Computer Corporation POSIX Thread Architect |
    | My book: http://www.awl.com/cseng/titles/0-201-63392-2/ |
    \—–[ http://home.earthlink.net/~anneart/family/dave.html ]—–/

Leave a Reply

Your email address will not be published. Required fields are marked *