簡體   English   中英

阻塞隊列和多線程消費者,如何知道何時停止

[英]Blocking queue and multi-threaded consumer, how to know when to stop

我有一個單線程生成器,它創建一些任務對象,然后將其添加到ArrayBlockingQueue (具有固定大小)。

我也開始了一個多線程的消費者。 這是構建為固定線程池( Executors.newFixedThreadPool(threadCount); )。 然后我向這個threadPool提交了一些ConsumerWorker入口,每個ConsumerWorker都對上面提到的ArrayBlockingQueue實例進行了引用。

每個這樣的Worker都會對隊列執行take()並處理任務。

我的問題是,當沒有更多的工作要做時,讓工人知道的最佳方法是什么。 換句話說,如何告訴Workers,生產者已經完成了對隊列的添加,從這一點開始,每個工作人員在看到Queue為空時應該停止。

我現在得到的是一個設置,我的Producer初始化了一個回調,當他完成它的工作(向隊列中添加東西)時會觸發回調。 我還保留了我創建並提交給ThreadPool的所有ConsumerWorkers的列表。 當Producer Callback告訴我生產者已完成時,我可以告訴每個工人。 此時,他們應該只是繼續檢查隊列是否為空,當它變為空時它們應該停止,從而允許我優雅地關閉ExecutorService線程池。 就是這樣的

public class ConsumerWorker implements Runnable{

private BlockingQueue<Produced> inputQueue;
private volatile boolean isRunning = true;

public ConsumerWorker(BlockingQueue<Produced> inputQueue) {
    this.inputQueue = inputQueue;
}

@Override
public void run() {
    //worker loop keeps taking en element from the queue as long as the producer is still running or as 
    //long as the queue is not empty:
    while(isRunning || !inputQueue.isEmpty()) {
        System.out.println("Consumer "+Thread.currentThread().getName()+" START");
        try {
            Object queueElement = inputQueue.take();
            //process queueElement
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//this is used to signal from the main thread that he producer has finished adding stuff to the queue
public void setRunning(boolean isRunning) {
    this.isRunning = isRunning;
}

}

這里的問題是我有一個明顯的競爭條件,有時生產者將完成,發出信號,消費者工作者將在消耗隊列中的所有內容之前停止。

我的問題是,同步這個的最佳方法是什么,以便一切正常? 我是否應該同步整個部分來檢查生產者是否正在運行加上如果隊列是空的加上從隊列中取出一些塊(在隊列對象上)? 我應該只在ConsumerWorker實例上同步isRunning布爾的更新嗎? 還有其他建議嗎?

更新,這里是我最終使用的工作實現:

public class ConsumerWorker implements Runnable{

private BlockingQueue<Produced> inputQueue;

private final static Produced POISON = new Produced(-1); 

public ConsumerWorker(BlockingQueue<Produced> inputQueue) {
    this.inputQueue = inputQueue;
}

@Override
public void run() {
    //worker loop keeps taking en element from the queue as long as the producer is still running or as 
    //long as the queue is not empty:
    while(true) {
        System.out.println("Consumer "+Thread.currentThread().getName()+" START");
        try {
            Produced queueElement = inputQueue.take();
            Thread.sleep(new Random().nextInt(100));
            if(queueElement==POISON) {
                break;
            }
            //process queueElement
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Consumer "+Thread.currentThread().getName()+" END");
    }
}

//this is used to signal from the main thread that he producer has finished adding stuff to the queue
public void stopRunning() {
    try {
        inputQueue.put(POISON);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

}

這很大程度上受到了JohnVint在下面的回答的啟發,只有一些小修改。

===由於@ vendhan的評論而更新。

謝謝你的觀察。 你是對的,這個問題中的第一個代碼片段(在其他問題中)是while(isRunning || !inputQueue.isEmpty())沒有意義的while(isRunning || !inputQueue.isEmpty())

在我實際的最終實現中,我做了一些更接近你更換“||”的建議。 (或)用“&&”(和),從某種意義上說,每個工人(消費者)現在只檢查他從列表中得到的元素是否是毒丸,如果是這樣,那么理論上我們可以說工人有要運行並且隊列不能為空)。

你應該繼續從隊列中take() 你可以使用毒丸告訴工人停止。 例如:

private final Object POISON_PILL = new Object();

@Override
public void run() {
    //worker loop keeps taking en element from the queue as long as the producer is still running or as 
    //long as the queue is not empty:
    while(isRunning) {
        System.out.println("Consumer "+Thread.currentThread().getName()+" START");
        try {
            Object queueElement = inputQueue.take();
            if(queueElement == POISON_PILL) {
                 inputQueue.add(POISON_PILL);//notify other threads to stop
                 return;
            }
            //process queueElement
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//this is used to signal from the main thread that he producer has finished adding stuff to the queue
public void finish() {
    //you can also clear here if you wanted
    isRunning = false;
    inputQueue.add(POISON_PILL);
}

我會給工人們發一個特殊的工作包來表示他們應該關閉:

public class ConsumerWorker implements Runnable{

private static final Produced DONE = new Produced();

private BlockingQueue<Produced> inputQueue;

public ConsumerWorker(BlockingQueue<Produced> inputQueue) {
    this.inputQueue = inputQueue;
}

@Override
public void run() {
    for (;;) {
        try {
            Produced item = inputQueue.take();
            if (item == DONE) {
                inputQueue.add(item); // keep in the queue so all workers stop
                break;
            }
            // process `item`
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

}

要停止worker,只需將ConsumerWorker.DONE添加到隊列中即可。

我們不能使用CountDownLatch實現它,其中size是生產者中的記錄數。 並且每個消費者都會在處理countDown后記錄下來。 當所有任務完成時,它會跨越awaits()方法。 然后停止所有你的消費者。 隨着所有記錄的處理。

在您嘗試從隊列中檢索元素的代碼塊中,使用poll(time,unit)而不是take()

try { 
    Object queueElement = inputQueue.poll(timeout,unit);
     //process queueElement        
 } catch (InterruptedException e) {
        if(!isRunning && queue.isEmpty())
         return ; 
 } 

通過指定適當的超時值,可以確保線程不會阻塞,以防有一個不幸的序列

  1. isRunning是真的
  2. 隊列變空,因此線程進入阻塞等待(如果使用take()
  3. isRunning設置為false

我不得不使用多線程生產者和多線程消費者。 我最終得到了一個Scheduler -- N Producers -- M Consumers方案,每個都通過隊列進行通信(總共兩個隊列)。 調度程序使用生成數據的請求填充第一個隊列,然后用N“毒丸”填充它。 有一個活躍的生產者計數器(原子int),最后一個接收最后一個毒丸的生產者將M毒丸送到消費者隊列。

您可以使用許多策略,但一個簡單的策略是使用任務的子類來表示作業的結束。 制作人不直接發送此信號。 相反,它將此任務子類的實例排入隊列。 當您的某個消費者完成此任務並執行它時,會導致信號被發送。

暫無
暫無

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

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