如何检测Selector.wakeup调用

时间:2008-12-02 10:37:33

标签: java nio

如果我愿意写:

int selectedChannels = selector.select();
Set selectedKeys = selector.selectedKeys();
if ( selectedChannels != selectedKeys.size() ) {
    // Selector.select() returned because of a call to Selector.wakeup()
    // so do synchronization.
}
// Continue with handling selected channels.

会正确检测到唤醒呼叫吗?

Backgroundinformation:

我正在编写一个服务器,大部分时间只接收数据包并将它们存储在一个文件中。应用程序很少需要向自己发送特殊数据包。为此,它启动连接(从不同的线程)到服务器套接字:

SocketChannel channel = SocketChannel.open();
channel.configureBlocking( false );
channel.connect( new InetSocketAddress( InetAddress.getLocalHost(), PORT ));
selector.wakeup();
SelectionKey key = channel.register( selector, SelectionKey.OP_CONNECT );

问题是如果主线程已经在Selector.select()中,SelectableChannel.register()可能会阻塞。为了防止这种情况发生,我正在调用Selector.wakeup(),让主线程从select()过早返回。为了确保其他线程有机会完成注册调用,我必须同步主线程,但是我必须在每次从select()返回后执行此操作。如果我可以检测是否因为wakeup()调用而从select()返回,那么我可以针对这种情况对其进行优化。

所以,从理论上讲,顶级代码片段应该可行,但我想知道它是否只会这样做,因为它依赖于一些未指明的行为?

感谢任何提示。

4 个答案:

答案 0 :(得分:3)

我认为根据Selector#select()Selector#selectedKeys()的合同,原则上提议的代码段根本不起作用。来自Selector

  
      
  • 所选择的密钥集是一组密钥,使得检测到每个密钥的信道准备好用于在先前的选择操作期间在密钥的兴趣集中识别的至少一个操作。这个集合由selectedKeys方法返回。
  •   
public abstract int select(long timeout)
                throws IOException
    Returns:
        The number of keys, possibly zero, whose ready-operation sets were
        updated

正如我所读到的那样,selectedKeys集的大小应始终等于select根据定义返回的数字。我注意到 - 你可能也有 - 一些实现并不完全遵循文档,事实上selectedKeys返回所有具有更新的就绪操作集的键,即使它们在调用期间没有更新select。由于调用wakeup而选择唤醒的唯一其他指示符可能是键的数量为零;但是,任何一种方法最多都是不可靠的。

正如通过并发控制所暗示的那样,处理此问题的常用方法。我不担心这里的执行时间;这是premature optimization的典型示例。

除非你真的担心单位数的微秒容差,否则你不会注意到任何减速 - 如果你担心这种容忍程度,Selector对你来说不够可靠反正。

以下是通常机制的一个示例,使用ReentrantLock来实现适当的并发:

ReentrantLock selectorGuard;
Selector selector;

private void doSelect() {
    // Don't enter a select if another thread is in a critical block
    selectorGuard.lock();
    selectorGuard.unlock();

    selector.select();
    Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();

    while(keyIter.hasNext()) {

        SelectionKey key = keyIter.next();
        keyIter.remove();

        // Process key
    }
}

private void addToSelector() {

    // Lock the selector guard to prevent another select until complete
    selectorGuard.lock();

    try {
        selector.wakeup();

        // Do logic that registers channel with selector appropriately

    } finally {
        selectorGuard.unlock();
    }
}

答案 1 :(得分:0)

我不明白为什么你的代码会起作用。

为什么不在volatile后查看select

答案 2 :(得分:0)

如果select()返回零,则表示超时或被唤醒。

答案 3 :(得分:-1)

你无法确定选择器醒来的唯一原因是唤醒呼叫。您可能还有套接字活动。

所以,你需要让唤醒的调用者也做一些像设置一个volatile布尔值来表示它需要注意的东西。选择器循环可以在每次唤醒时检查此布尔值。