5

如果我要写:

int selectedChannels = selector.select();
设置 selectedKeys = selector.selectedKeys();
if ( selectedChannels != selectedKeys.size() ) {
    // Selector.select() 因为调用了 Selector.wakeup() 而返回
    // 同步也是如此。
}
// 继续处理选定的通道。

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

背景资料:

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

SocketChannel 通道 = SocketChannel.open();
通道.configureBlocking(假);
channel.connect(new InetSocketAddress(InetAddress.getLocalHost(), PORT));
选择器.唤醒();
SelectionKey key = channel.register( selector, SelectionKey.OP_CONNECT );

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

所以,理论上最上面的代码片段应该可以工作,但我想知道它是否只会这样做,因为它依赖于一些未指定的行为?

感谢您的任何提示。

4

4 回答 4

3

Selector#select()我猜根据and的合同,建议的代码段原则上根本不起作用Selector#selectedKeys()。从选择器

  • 所选键集是一组键,使得在先前的选择操作期间检测到每个键的通道已准备好用于在键的兴趣集中标识的至少一个操作。该集合由 selectedKeys 方法返回。
公共抽象 int 选择(长时间超时)
                抛出 IOException
    回报:
        键的数量,可能为零,其就绪操作集为
        更新

正如我所读到的,selectedKeys集合的大小应始终等于select定义返回的数字。我已经注意到 - 正如您可能已经注意到的那样 - 一些实现并不完全遵循文档,实际上selectedKeys返回所有具有更新就绪操作集的键,即使它们在调用select. 选择因调用而唤醒的唯一其他指标wakeup可能是键的数量为零;但是,这两种方法充其量都是不可靠的。

处理这个问题的常用方法是,隐含地,通过并发控制。我不会担心这里的执行时间;这是过早优化的经典例子。

除非您真的担心个位数的微秒容差,否则您不会注意到任何减速 - 如果您担心该级别的容差,Selector无论如何 a 对您来说都不够可靠。

这是一个常用机制的示例,使用 aReentrantLock来完成适当的并发:

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();
    }
}
于 2008-12-02T17:08:16.577 回答
0

我不明白为什么您的代码通常可以正常工作。

为什么不只检查一个volatileafter select

于 2008-12-02T12:02:35.633 回答
0

如果select()返回零,要么超时,要么被唤醒。

于 2017-05-01T23:52:21.887 回答
-1

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

因此,您需要让 wakeup 的调用者也做一些事情,比如设置一个 volatile 布尔值来表示它对注意力的渴望。选择器循环可以在每次唤醒时检查此布尔值的值。

于 2008-12-02T13:43:27.270 回答