2017-10-07 81 views
5

考慮請求 - 響應協議。如何在請求處理線程和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附件?一些其他形式的同步?)

+0

這似乎是[這個問題](https://stackoverflow.com/q/28754275/149138),它也沒有很好的答案。我的回答可能是,在實際中,在一個線程上的wakeup()和另一個接收喚醒的線程上的select()調用之間發生了一個事件之前的關係。這有兩個實際原因:因爲它以任何其他方式工作都會使API非常無用,典型實現的內部機制將涉及相同類型的原子和鎖定原語,這些原語會在訂購前執行。我認爲這是一個文檔缺陷。 – BeeOnRope

回答

2

documentation on Selector說以下內容:

選擇操作同步的選擇本身,就按鍵,並在選擇鍵集中,按照這個順序。

happens-before relationJava Language Specification, Chapter 17定義爲包括同步-與關係。

即便如此,您應該在附加的對象上正確同步。這是你的目標,這是你的責任。假設只有您的代碼在執行程序的線程中寫入responseBuffer,並且只有選擇器線程在之後從中讀取,您說您對寫入可用性感興趣,則您有足夠的同步。

什麼可能會讓你驚訝的是,你從interestOps(...),即使在wakeup()之前獲得同步。


從我的經驗,如果你有一個很難試圖實現通過圖書館事業的正確同步(在這種情況下,選擇器),你最好同步對象自己,例如關於對象本身的synchronize聲明,ReentrantLock,您在對象操作中使用的其他一些常見同步對象等等。您失去了一點性能(實際上,在大多數情況下,微不足道,假設您不在被守護部分)保持冷靜。

3

首先,您不需要通知選擇器想要寫入。你只是寫。只有在寫入返回零的情況下,才需要涉及選擇器或其線程。

其次,由於選擇器的三個同步級別,發生了之前發生的關係,前提是您還進行了同步,如下所示。

如果選擇器當前正在選擇,您的代碼可能會在中調用interestOps()。這種可能性不會被Javadoc排除。您需要按照正確的順序執行操作:

  1. 喚醒。
  2. 在選擇器上同步。
  3. 致電interestOps()

(2)和選擇器自身內部同步的組合建立了任何必要的發生之前的關係。

+0

您目前選擇_是什麼意思?只要'select()'方法調用還沒有返回(當它被阻塞或者正在準備其所有的集合時)?當選擇器線程被阻塞時,我無法在'interestOps(..)'中重現阻塞在'select()'中。 – Savior

+0

您的建議順序會通過'synchronized(選擇器)'添加發生前的事件。我假設你建議這樣做,因爲_選擇操作按照選擇器本身,按鍵集和選定鍵集上的順序同步._正確嗎?如果進入阻塞狀態,'select'是否會釋放它的'Selector'上的鎖?這是記錄在任何地方還是我們想要承擔?如果'select'線程能夠在1和2之間重新調用'select',會發生什麼情況。 – Savior

+0

'當前選擇'表示與''還沒有從'select()'返回。選擇器在阻塞時不會釋放這三個鎖:它不能,因爲它們是同步,因此它來自您引用的文本。如果它在1和2之間重新調用select(),則2將等到select()返回下一個。 – EJP

相關問題