选择器.select()没有像预期的那样阻塞。

7
在我的当前项目中,我注意到select()未按预期阻塞。它根本不会阻塞并且总是返回,即使没有IO存在。所以我的CPU很忙。
注册将始终由另一个线程调用,因此我需要锁定和唤醒。
文档对于selectNow()说:

调用此方法会清除任何先前调用唤醒方法的影响。

所以我在每次迭代结束时调用该方法。但是没有成功。我找不到如何使用selectNow达到我的目的的示例或解释。
代码有什么问题?
这是我的示例代码,您可以测试一下。
顺便说一句:另一个stackoverflow问题是我的代码的样板。编辑:示例已修复!现在它可以工作了。
import java.io.IOException;
import java.net.*;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.concurrent.locks.ReentrantLock;

public class Test implements Runnable {
    ReentrantLock selectorLock = new ReentrantLock();
    Selector selector;
    boolean alive;

    @Override
    public void run() {
        SelectionKey key;
        Iterator<SelectionKey> keys;

        alive = true;
        try {
            while (alive) {
                selectorLock.lock();
                selectorLock.unlock();

                selector.select();
                System.out.println("select() returned");

                keys = selector.selectedKeys().iterator();
                // handle each "event"
                while (keys.hasNext()) {
                    key = keys.next();
                    // mark as handled
                    keys.remove();
                    // handle
                    handleKey(key);
                }
                //selector.selectNow(); // don't fix this
            }
        } catch ( IOException e ) {
            e.printStackTrace();
        }
    }

    private void handleKey(SelectionKey key)
        throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        if (key.isConnectable()) {
            System.out.println("connecting");
            if ( channel.finishConnect() ) {
                key.interestOps(SelectionKey.OP_READ);
            } else {
                key.cancel();
            }
        } else if (key.isReadable()) {
            System.out.println("reading");
            // read and detect remote close
            channel.read(ByteBuffer.allocate(64));
        }
    }

    public void register(SelectableChannel channel, int ops, Object attachment)
        throws ClosedChannelException {
        selectorLock.lock();
        try {
            System.out.println("wakeup");
            selector.wakeup();
            channel.register(selector, ops, attachment);
        } finally {
            selectorLock.unlock();
        }
    }

    public Test()
        throws IOException {
        selector = Selector.open();
    }

    public static void main(String[] args)
        throws IOException {
        Test t = new Test();
        new Thread(t).start();

        SocketAddress address = new InetSocketAddress("localhost", 8080);
        SocketChannel channel = SocketChannel.open();
        channel.configureBlocking(false);
        channel.connect(address);

        t.register(channel, SelectionKey.OP_CONNECT, "test channel attachment");
    }
}
1个回答

12

OP_CONNECT 触发并且 finishConnect() 返回 'true' 后再注册 OP_READ, 在这一点上,您必须注销 OP_CONNECT

同样,在你有写入内容时不要注册 OP_WRITE。除了套接字发送缓冲区已满时,OP_WRITE 总是就绪的,所以它应该在您检测到该条件( write() 返回零)之后才进行注册,而且立即在触发后注销它(除非该条件再次出现)。

最后,OP_CONNECTOP_WRITE 实际上是相同的东西,这也解释了您选择器无限循环的原因。


谢谢。你的提示解决了问题。 我改变了 handleKey - Marcel Jaeschke
你如何取消注册?https://dev59.com/XWYr5IYBdhLWcg3wNHmh#13867557 - topher217
您可以通过在所需的selectionKey上调用interestOps方法并将其设置为您现在想要收到通知的内容来取消注册特定的OP_*。例如:selectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); - Tonsic
我认为只有在检测到短计数后才应该注册OP_WRITE。使用OP_WRITE驱动输出非常好且实用。当您想要写入时,只需切换OP_WRITE即可。选择器线程将编码+写入,直到缓冲区为空,此时它会切换OP_WRITE关闭。干净。在内部,poll(2)描述符将OP_WRITE表示为标志中的另一个位。至少在*nix上是这样的。因此,我想不到任何开销或其他原因来使用OP_WRITE来驱动输出。 - squarewav

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接