如何在请求处理线程和 SocketChannel 选择器线程之间建立 happens-before 关系?

How to establish a happens-before relationship between a request handling thread and a SocketChannel selector thread?

考虑请求-响应协议。

我们生成一个线程来执行 select() 循环,以便在已接受的非阻塞 SocketChannel 上进行读取和写入。这可能看起来像

while (!isStopped()) {
    selector.select();
    Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();

    while (selectedKeys.hasNext()) {
        SelectionKey selectedKey = selectedKeys.next();
        selectedKeys.remove();
        Context context = (Context) selectedKey.attachment();
        if (selectedKey.isReadable()) {
            context.readRequest();
        } else /* if (selectedKey.isWritable()) */ {
            context.writeResponse();
        }
    }
}

其中 Context 只是相应 SocketChannel 的容器,一个缓冲区和逻辑来读取和写入它。 readRequest 可能看起来像

public void readRequest() {
    // read all content
    socketChannel.read(requestBuffer);
    // not interested anymore
    selectionKey.interestOps(0);
    executorService.submit(() -> {
        // handle request with request buffer and prepare response
        responseBuffer.put(/* some response content */); // or set fields of some bean that will be serialized

        // notify selector, ready to write
        selectionKey.interestOps(SelectionKey.OP_WRITE);
        selectionKey.selector().wakeup(); // worried about this
    });
}

换句话说,我们从套接字通道读取,填充一些缓冲区并将处理交给其他线程。该线程进行处理并准备一个响应,并将其存储在响应缓冲区中。然后它通知选择器它想要写入并唤醒它。

Selector#wakeup() 的 Javadoc 没有提及任何先行发生关系,因此我担心选择器线程可能会看到响应缓冲区(或某些中间对象)处于不一致状态。

有这种可能吗?如果是,那么通过 Selector 循环线程传递要写入 SocketChannel 的响应的正确方法是什么? (通过某些 volatile 字段发布响应?使用 SelectionKey 附件?某种其他形式的同步?)

首先,您不需要通知选择者要写。你随便写。只有在写入返回零的情况下才需要涉及选择器或其线程。

其次,happens-before 关系是选择器的三级同步的结果,前提是您也进行了同步,如下所示。

如果选择器当前正在选择,您的代码可能会在 interestOps() 调用中 阻塞 。 Javadoc 不排除这种可能性。您需要按照正确的顺序进行操作:

  1. 醒来。
  2. 在选择器上同步。
  3. 呼叫interestOps().

(2) 和选择器自身的内部同步的组合建立了任何必要的先行关系。

documentation on Selector 表示如下:

The selection operations synchronize on the selector itself, on the key set, and on the selected-key set, in that order.

happens-before relation is defined in the Java Language Specification, Chapter 17,包括 同步的关系。

即便如此,您也应该在附加对象上正确同步。那是你的目标,这是你的职责。假设只有您的代码在执行程序的线程中写入 responseBuffer,并且只有选择器线程从中读取 您说您对写入可用性感兴趣之后,您有足够的同步。

可能会让您感到惊讶的是,您甚至在 wakeup().

之前就从 interestOps(...) 获得了同步

根据我的经验,如果您很难尝试通过库实用程序(在本例中为选择器)实现正确的同步,您最好自己同步您的对象,例如使用对象本身的 synchronize 语句,ReentrantLock,您在对象操作中使用的其他一些常见同步对象等。您会损失一点点性能(实际上,在大多数情况下微不足道,假设你不要挡在守卫区内)以保持冷静。