锁定老帖子 主题:Java NIO 的 wakeup 剖析
精华帖 (0) :: 良好帖 (15) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-08-18
java NIO的实现中,有不少细节点非常有学习意义的,就好比下面的这个点: wakeup()准确来说,应该是Selector的wakeup(),即Selector的唤醒,为什么要有这个唤醒操作呢?那还得从Selector的选择方式
来说明,前文已经总结过Selector的选择方式有三种:select()、select(timeout)、selectNow()。 wakeup的原理既然Selector阻塞式选择因为找到感兴趣事件ready才会返回(排除超时、中断),就给它构造一个感兴趣事件ready的场景即可。下图可以比较形象的形容wakeup原理: Selector管辖的FD(文件描述符,linux即为fd,对应一个文件,windows下对应一个句柄;每个可选择Channel在创建的时 候,就生成了与其对应的FD,Channel与FD的联系见另一篇)中包含某一个FD A, A对数据可读事件感兴趣,当往图中漏斗端放入(写入)数据,数据会流进A,于是A有感兴趣事件ready,最终,select得到结果而返回。 wakeup在Selector中的定义如下: 下面结合上图来追寻wakeup的实现:
public Selector wakeup() { synchronized (interruptLock) { if (!interruptTriggered) { pollWrapper.interrupt(); interruptTriggered = true; } } return this; } window下Selector的实现为WindowsSelectorImpl,其wakeup实现如下:
public Selector wakeup() { synchronized (interruptLock) { if (!interruptTriggered) { setWakeupSocket(); interruptTriggered = true; } } return this; } 其中interruptTriggered为中断已触发标志,当pollWrapper.interrupt()之后,该标志即为true了;得益于这个标志,连续两次wakeup,只会有一次效果。
对比上图及上述代码,其实pollWrapper.interrupt()及setWakeupSocket()就是图中的往漏斗中倒水的过程,不 管windows也好,linux也好,它们wakeup的思想是完全一致的,不同的地方就在于实现的细节了,例如上图中漏斗与通道的链接部 分,linux下是采用管道pipe来实现的,而windows下是采用两个socket之间的通讯来实现的,它们都有这样的特性:1)都有两个端,一个 是read端,一个是write端,windows中两个socket也是一个扮演read的角色,一个扮演write的角色;2)当往write端写入 数据,则read端即可以收到数据;从它们的特性可以看出,它们是能够胜任这份工作的。
如果只想理解wakeup的原理,看到这里应该差不多了,不过,下面,想继续深入一下,满足更多人的好奇心。
1) 准备阶段PollSelector在构造的时候,就将管道pipe,及wakeup专用FD给准备好,可以看一下它的实现:
PollSelectorImpl(SelectorProvider sp) { super(sp, 1, 1); int[] fdes = new int[2]; IOUtil.initPipe(fdes, false); fd0 = fdes[0]; fd1 = fdes[1]; pollWrapper = new PollArrayWrapper(INIT_CAP); pollWrapper.initInterrupt(fd0, fd1); channelArray = new SelectionKeyImpl[INIT_CAP]; }
IOUtil.initPipe,采用系统调用pipe(int fd[2])来创建管道,fd[0]即为ready端,fd[1]即为write端。
void initInterrupt(int fd0, int fd1) { interruptFD = fd1; putDescriptor(0, fd0); putEventOps(0, POLLIN); putReventOps(0, 0); } 以看到,initInterrupt在准备wakeup专用FD,因为fd0是read端fd,fd1是write端fd: 2) 执行阶段有了前面的准备工作,就看PollArrayWrapper中的interrupt()实现:
public void interrupt() { interrupt(interruptFD); } interrupt是native方法,它的入参interruptFD即为准备阶段管道的write端fd,对应于上图,其实就是漏斗端,因此,就是不看其实现,也知道它肯定扮演着倒水的这个动作,看其实现:
JNIEXPORT void JNICALL Java_sun_nio_ch_PollArrayWrapper_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"); } } 可以看出,interrupt(interruptFD)是往管道的write端fd1中写入一个字节(write(fd, fakebuf, 1))。 EPollSelector与PollSelector相比,其wakeup实现就只有initInterrupt不同,它的实现如下:
void initInterrupt(int fd0, int fd1) { outgoingInterruptFD = fd1; incomingInterruptFD = fd0; epollCtl(epfd, EPOLL_CTL_ADD, fd0, EPOLLIN); } epfd之前的篇章里已经讲过,它是通过epoll_create创建出来的epoll文件fd,epollCtl调用内核epoll_ctl实现了往epfd上添加fd0,且其感兴趣事件为可读(EPOLLIN)。
关于wakeup,这里还有两个疑问:
也欢迎大家留下自己的想法,一起讨论。
ps. 本文为作者原创(java nio 教程
) 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
浏览 2510 次