2012-03-18 88 views
3

我有一個1生產者,M消費者線程模式。製作人從光盤讀取原始文檔並將其放置在LinkedBlockingQueue中。然後,每個消費者線程需要一個原始文件,並使用一類多線程Java正則表達式

ParsedDoc article = parseDoc(rawDocument); 

的parseDoc類是一套大約20個方法有以下模式解析文檔:

public String clearContent(String document) { 
    Pattern regex = Pattern.compile(pattern); 
    Matcher matcher = regex.matcher(document); 
    matcher.find(); 
    .... 
} 

public String removeHTML(String document) { 
    Pattern regex = Pattern.compile(pattern); 
    Matcher matcher = regex.matcher(document); 
    matcher.replaceAll(""); 
    .... 
} 

我現在面臨的問題是代碼在本地(2核)機器上運行得相當快。但是當我在一臺8核心機器上運行相同的代碼時,消費者的性能會降低,幾乎停滯不前。我試圖優化jvm選項無濟於事。除去正則表達式處理步驟導致了8核心上預期的x4性能提升。所以問題是正則表達式。我意識到Pattern是線程安全的,匹配器可能需要重置()。但問題是如何重新設計正則表達式庫(在parseDoc類中),以便跨M個消費者線程安全。

任何幫助,將不勝感激

謝謝

回答

1

生產者消費者模式不能很好地擴展。 越是生產者或消費者,你的表現越差。原因是普通隊列成爲整個系統的瓶頸。我希望你看到如何。

更好的方法是沒有共同的隊列;讓每個消費者都擁有自己的隊列。當請求進入時,它會轉到負載均衡器。負載均衡器會將請求放在最小的使用者隊列中。平衡器成爲瓶頸,但它並沒有做很多操作 - 只需選擇合適的隊列發送傳入的請求 - 所以它應該快速。

這裏是一個編輯爲您解答: Problem (more in depth):你有慢它獲取更多的內核。爲什麼?共享內存。

@Peyman使用ConcurrentLinkedQueue(這是一個非阻塞等待空閒隊列,其中一個隊列和一個隊列可以同時進行)。即使在最初的設計中嘗試它,並對兩種設計進行基準測試我希望你的修改後的設計能夠更好地執行,因爲你可以同時只有1個隊列和1個隊列,而不是像你最初的設計那樣只有一個隊列和n個隊列(但這只是我的猜測)。

一個great paper on scalable consumer producer by using balancers

this page(或者可以只查看「從普通工人隊列的方法來排隊,每個線程的方式遷移」)

下面是從http://www.javaperformancetuning.com/news/newtips128.shtml列表。我認爲最後3分給你更適用:

  • 大多數服務器應用程序使用一個普通工人隊列和線程池;共享工作者隊列包含從遠程來源到達的短任務;線程池從隊列中檢索任務並處理任務;如果沒有要處理的任務,則線程在隊列上被阻塞。
  • 當任務數量很多且任務時間很短時,線程之間共享的饋線隊列是一個訪問瓶頸(來自爭用)。使用的核心越多,瓶頸越嚴重。可用於訪問共享隊列克服爭
  • 解決方案包括:使用無鎖的數據結構;使用具有多個鎖的併發數據結構;維護多個隊列以隔離爭用。
  • 甲隊列每線程方法消除隊列訪問競爭,但是當一個隊列被清空,而有未處理的其他隊列中排隊的數據是不是最佳的。爲了改善這一點,空閒線程應該有能力從其他隊列中竊取工作。爲了將爭用保持在最低限度,應該從另一個隊列的尾部(從隊列頭部完成線程自己的隊列中的正常出隊隊列)完成'steal'。
+1

在大多數情況下,所用的時間壓入/彈出一個任務,(即32/64位指針/參考)中,從一個隊列是多少,比處理任務&所以爭用所花費的時間要小得多不是問題。如果開發人員堅持把工作推到一個P-C隊列上,那麼只需將兩個整數加在一起,那麼當然,這不會很好地擴展。在任何合理的數據集上進行模式匹配應該適用於線程池。 – 2012-03-18 10:07:08

+1

您是否有任何負載均衡器的例子,比普通阻塞隊列的推送/彈出消耗少得多的時間? – 2012-03-18 10:11:21

+0

嗨阿德里安。我不確定是否正確實施了負載平衡器,但我確實遵循了您的建議。不幸的是我看不到改進。事實上,它在8核心機器上變得更糟。每個線程現在都有一個本地鏈接列表隊列,並且生產者線程讀取該線路並執行負載平衡,根據他們擁有多少負載填充每個線程的隊列。非阻塞是否爲每個消費者線程正確的數據結構? – Peyman 2012-03-19 04:23:44

1

編譯正則表達式很慢。你應該只對一個給定的模式做一次。除非您的示例中顯示的pattern變量對於每次調用實際上都不相同,否則Pattern實例可能類別成員爲static。 A Pattern對於多線程的併發使用是明確安全的。 (所有可修改狀態由Matcher保存。)

由於Matcher僅限於單個線程的堆棧,因此不需要擔心任何線程問題。不要嘗試重複使用Matcher。它可以完成,但如果回收它們比正則表達式編譯節省很多時間,我會感到驚訝。

+0

嗨埃裏克。按照建議,我嘗試將編譯後的正則表達式從類中移出,但收益不大。 – Peyman 2012-03-19 04:18:52

+0

您的性能分析是否表明所有8個內核的CPU使用率均爲100%?你能確定消費者不只是等待制片人從磁盤讀取另一個文件嗎? – erickson 2012-03-19 04:40:29

0

如果由於無法控制的同步而未能獲得所期望的併發性,想到的一個解決方案是將工作分派到子進程(其他JVM)達到核心數-1。