庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

Selector.wakeup实现注记

Posted on 2010-10-22 10:35 dennis 阅读(6220) 评论(3)  编辑  收藏 所属分类: java源码解读

    NIO中的Selector封装了底层的系统调用,其中wakeup用于唤醒阻塞在select方法上的线程,它的实现很简单,在linux上就是创建一个管道并加入poll的fd集合,wakeup就是往管道里写一个字节,那么阻塞的poll方法有数据可读就立即返回。证明这一点很简单,strace即可知道:
public class SelectorTest {
    
public static void main(String[] args) throws Exception {
        Selector selector 
= Selector.open();
        selector.wakeup();
    }
}

     使用strace调用,只关心write的系统调用
sudo strace --e write java SelectorTest

     输出:
Process 29181 attached
Process 
29182 attached
Process 
29183 attached
Process 
29184 attached
Process 
29185 attached
Process 
29186 attached
Process 
29187 attached
Process 
29188 attached
Process 
29189 attached
Process 
29190 attached
Process 
29191 attached
[pid 
29181] write(36"\1"1)          = 1
Process 
29191 detached
Process 
29184 detached
Process 
29181 detached

    有的同学说了,怎么证明这个write是wakeup方法调用的,而不是其他方法呢,这个很好证明,我们多调用几次:
public class SelectorTest {
    
public static void main(String[] args) throws Exception {
        Selector selector 
= Selector.open();
        selector.wakeup();
        selector.selectNow();
        selector.wakeup();
        selector.selectNow();
        selector.wakeup();
    }
}

    修改程序调用三次wakeup,心细的朋友肯定注意到我们还调用了两次selectNow,这是因为在两次成功的select方法之间调用wakeup多次都只算做一次,为了显示3次write,这里就每次调用前select一下将前一次写入的字节读到,同样执行上面的strace调用,输出:

Process 29303 attached
Process 
29304 attached
Process 
29305 attached
Process 
29306 attached
Process 
29307 attached
Process 
29308 attached
Process 
29309 attached
Process 
29310 attached
Process 
29311 attached
Process 
29312 attached
Process 
29313 attached
[pid 
29303] write(36"\1"1)          = 1
[pid 
29303] write(36"\1"1)          = 1
[pid 
29303] write(36"\1"1)          = 1
Process 
29313 detached
Process 
29309 detached
Process 
29306 detached
Process 
29303 detached

     果然是3次write的系统调用,都是写入一个字节,如果我们去掉selectNow,那么三次wakeup还是等于一次:
public class SelectorTest {
    
public static void main(String[] args) throws Exception {
        Selector selector 
= Selector.open();
        selector.wakeup();
        selector.wakeup();
        selector.wakeup();
    }
}
 
   输出:
Process 29331 attached
Process 
29332 attached
Process 
29333 attached
Process 
29334 attached
Process 
29335 attached
Process 
29336 attached
Process 
29337 attached
Process 
29338 attached
Process 
29339 attached
Process 
29340 attached
Process 
29341 attached
[pid 
29331] write(36"\1"1)          = 1
Process 
29341 detached
Process 
29337 detached
Process 
29334 detached
Process 
29331 detached

      wakeup方法的API说明没有欺骗我们。wakeup方法的API还告诉我们,如果当前Selector没有阻塞在select方法上,那么本次wakeup调用会在下一次select阻塞的时候生效,这个道理很简单,wakeup方法写入一个字节,下次poll等待的时候立即发现可读并返回,因此不会阻塞。

     具体到源码级别,在linux平台上的wakeup方法其实调用了pipe创建了管道,wakeup调用了EPollArrayWrapperinterrupt方法:
public  void interrupt() 

{
        interrupt(outgoingInterruptFD);
}

    实际调用的是interrupt(fd)的native方法,查看EPollArrayWrapper.c可见清晰的write系统调用:

JNIEXPORT 
void JNICALL
Java_sun_nio_ch_EPollArrayWrapper_interrupt(JNIEnv 
*env, jobject this, jint fd)
{
    
int fakebuf[1];
    fakebuf[
0= 1;
    
if (write(fd, fakebuf, 1< 0) {
        JNU_ThrowIOExceptionWithLastError(env,
"write to interrupt fd failed");
    }
}
    写入一个字节的fakebuf。有朋友问起这个问题,写个注记在此。strace充分利用对了解这些细节很有帮助。
 


评论

# re: Selector.wakeup实现注记  回复  更多评论   

2010-10-22 11:01 by BucketLI
好像在网络IO中很有用的一个命令,记下了。
然后能否多分享一些其他有用的网络命令?

# re: Selector.wakeup实现注记  回复  更多评论   

2010-10-22 22:19 by 上情下爱
不错,谢谢~
欣赏博主的钻研和分享精神

# re: Selector.wakeup实现注记  回复  更多评论   

2011-05-09 21:56 by niumd
读了mina、netty的源码,但是在阅读您的文章后受益匪浅,觉得自己理解还不够深入,希望那个楼主在多处精彩文章;谢谢分享。

只有注册用户登录后才能发表评论。


网站导航: