• 2007-03-06

    等在循环内

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://www.blogbus.com/dreamhead-logs/4704998.html

    《Effective Java》的第50个条款,不要在循环外调用wait。其中告诉我们,wait的标准用法如下:

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

    我想讨论的是并不是Java,所以,具体的讲解见《Effective Java》。

    我们知道,Java是跨平台的,所以,它也必然存在平台相关的部分。在Unix类OS上,wait可以通过条件变量实现,而在Windows平台上,我们会借助event对象的力量。

    最近写了这样一段代码。
    Lock(lock);
    while (<condition does not hold>) {
        Wait(event, lock);
    }
    Unlock(lock);

    这段代码用在一个线程池的实现,如果任务队列为空,就要等待。其实,这段代码同前面列举的Java代码可谓异曲同工。另外,有接口负责向任务队列中推送新任务,其实现的最后是一个通知。
    Notify(event);

    这两段代码中Wait和Notify在Windows平台就是利用event对象实现的。

    其实,这段代码运行没有什么问题,只是加上一些打印语句后,我发现了一个奇怪的问题:有时候需要wait两次代码才会停住。

    按照我的理解,当任务队列中没有消息的时候,满足wait条件,那么只要调用wait,代码就应该停住。但事实是有时候需要两次,也就是说,第一次wait没有挡住它。

    最终,absurd的一篇文章解答了我的困惑:
    小心pthread_cond_signal和SetEvent之间的差异

    实际上,当我把任务推到队列中,调用notify会给event对象置一个状态,也就是通知那些等待线程。正常的话,这个执行之后,状态会恢复(在Windows上使用auto方式)。但是,如果没有线程等待的话,差异就来了。

    如果没有线程等待,在Windows的实现中,状态是不会恢复的,而在Unix类OS上,这个状态会恢复。

    这样就可以解释前面的现象了。一开始任务推入队列的时候,会调用notify,但是这时并没有线程在等待,所以,在Windows上,状态没有恢复。这样的话,第一次wait,就可以顺利通过。当有线程通过wait,状态便会置回来。但当前还是无法满足运行条件,再次调用wait的时候,就会停住了。所以,这个wait必须放在循环中,如果按我们最直观的理解,简单的使用if判断的话,必然会出错。

    回到前面提到的Java实现,有了上面的讨论,现在可以从更底层来理解这个条款。所以,不仅仅是Java,只要我们打算让自己的程序更健壮,在循环里等待都是一个好办法。
    分享到:

    历史上的今天:

    引用地址: