簡體   English   中英

使用執行程序在Java中處理流的子流

[英]Processing sub-streams of a stream in Java using executors

我有一個程序可以處理通過網絡InputStream的巨大數據流(不是java.util.stream ,而是InputStream )。 流由對象組成,每個對象都有一種子流標識符。 現在,整個處理是在一個線程中完成的,但是這會占用大量CPU時間,並且每個子流都可以輕松地獨立進行處理,因此我正在考慮對其進行多線程處理。

但是,每個子流都需要保持大量的狀態,包括各種緩沖區,哈希映射等。 由於子流彼此獨立,因此沒有特別的理由使其並發或同步。 此外,每個子流都要求按照其到達的順序處理其對象,這意味着每個子流可能應該有一個線程(但是可能有一個線程處理多個子流)。

我正在考慮幾種解決方法,但是它們並不十分優雅。

  1. 為所有任務創建一個ThreadPoolExecutor 每個任務將包含下一個要處理的對象以及對保留所有狀態的Processor實例的引用。 這將確保必要的事前發生關系,從而確保處理線程將看到此子流的最新狀態。 據我所知,這種方法無法確保在同一線程中處理同一子流的下一個對象。 此外,還需要保證對象將按照它們進入的順序進行處理,這將需要Processor對象的額外同步,從而引入不必要的延遲。

  2. 手動創建多個單線程執行程序,以及一種將子流標識符映射到執行程序的哈希表。 這種方法需要手動管理執行器,在新的子流開始或結束時創建或關閉執行器,並在它們之間相應地分配任務。

  3. 創建一個自定義執行程序,該執行程序處理任務的特殊子類,每個子類都有一個子流ID。 該執行程序將其用作提示,以使用與上一個具有相同ID的線程相同的線程來執行此任務。 但是,我看不到實現這種執行程序的簡便方法。 不幸的是,似乎無法擴展任何現有的執行程序類,並且從頭實現執行程序有點過頭。

  4. 創建單個ThreadPoolExecutor ,而不是為每個傳入的對象創建任務,而是為每個子流創建一個長期運行的任務,該任務將阻塞並發隊列,等待下一個對象。 然后根據對象的子流ID將對象放入隊列中。 這種方法需要與子流一樣多的線程,因為任務將被阻塞。 子流的預期數量約為30-60,因此可以接受。

  5. 或者,按照4中的步驟進行,但限制線程數,將多個子流分配給單個任務。 這是2到4之間的混合體。據我所知,這是其中最好的方法,但是仍然需要在任務之間進行某種手動子流分配,並且需要一些方法來關閉額外的任務,例如子流結束。

確保每個子流在自己的線程中進行處理而又沒有大量容易出錯的代碼的最佳方法是什么? 這樣以下偽代碼將起作用:

// loop {
    Item next = stream.read();
    int id = next.getSubstreamID();
    Processor processor = getProcessor(id);
    SubstreamTask task = new SubstreamTask(processor, next, id);
    executor.submit(task); // This makes sure that the task will
                           // be executed in the same thread as the
                           // previous task with the same ID.
// } // loop

我建議使用一組單線程執行器。 如果可以為子流設計一致的哈希策略,則可以將子流映射到各個線程。 例如

final ExecutorsService[] es = ...

public void submit(int id, Runnable run) {
   es[(id & 0x7FFFFFFF) % es.length].submit(run);
}

密鑰可以是String也可以是long密鑰,但是可以通過某種方式來標識子流。 如果您知道特定的子流非常昂貴,則可以為其分配一個專用線程。

我最終選擇的解決方案如下所示:

private final Executor[] streamThreads
        = new Executor[Runtime.getRuntime().availableProcessors()];
{
    for (int i = 0; i < streamThreads.length; ++i) {
        streamThreads[i] = Executors.newSingleThreadExecutor();
    }
}
private final ConcurrentHashMap<SubstreamId, Integer>
        threadById = new ConcurrentHashMap<>();

此代碼確定使用哪個執行器:

    Message msg = in.readNext();
    SubstreamId msgSubstream = msg.getSubstreamId();
    int exe = threadById.computeIfAbsent(msgSubstream,
            id -> findBestExecutor());
    streamThreads[exe].execute(() -> {
        // processing goes here
    });

findBestExecutor()函數是這樣的:

private int findBestExecutor() {
    // Thread index -> substream count mapping:
    final int[] loads = new int[streamThreads.length];
    for (int thread : threadById.values()) {
        ++loads[thread];
    }
    // return the index of the minimum load
    return IntStream.range(0, streamThreads.length)
            .reduce((i, j) -> loads[i] <= loads[j] ? i : j)
            .orElse(0);
}

當然,這不是很有效,但是請注意,只有在出現新的子流時才會調用此函數(每隔幾個小時發生幾次,所以對我而言這沒什么大不了的)。 我的實際代碼看起來有些復雜,因為我有一種方法來確定兩個子流是否可能同時完成,如果可以,我會嘗試將它們分配給不同的線程,以在完成后保持均勻的負載。 但是,由於我從未在問題中提及此細節,因此我想它也不屬於答案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM