考慮請求 - 響應協議。如何在請求處理線程和SocketChannel選擇器線程之間建立一個前後關係?
我們產生了一個線程來執行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
});
}
換句話說,我們從套接字通道讀取,填充一些緩衝和掛斷處理一些其他線程。該線程完成處理並準備將其存儲在響應緩衝區中的響應。然後通知選擇器它想要寫入並喚醒它。
Javadoc Selector#wakeup()
沒有提及任何發生之前的關係,所以我擔心選擇器線程可能會看到響應緩衝區(或某個中間對象)處於不一致狀態。
這是一種可能的情況?如果是這樣的話,有什麼正確的方法可以通過Selector
循環線程將響應寫入SocketChannel
? (通過一些volatile
字段發佈響應?使用SelectionKey
附件?一些其他形式的同步?)
這似乎是[這個問題](https://stackoverflow.com/q/28754275/149138),它也沒有很好的答案。我的回答可能是,在實際中,在一個線程上的wakeup()和另一個接收喚醒的線程上的select()調用之間發生了一個事件之前的關係。這有兩個實際原因:因爲它以任何其他方式工作都會使API非常無用,典型實現的內部機制將涉及相同類型的原子和鎖定原語,這些原語會在訂購前執行。我認爲這是一個文檔缺陷。 – BeeOnRope