簡體   English   中英

兩個等待線程(生產者/消費者)與共享緩沖區

[英]Two waiting threads (producer/consumer) with a shared buffer

我試圖讓一堆生產者線程等待,直到緩沖區有一個項目的空間,然后它將項目放入緩沖區,如果沒有更多的空間,則回到睡眠狀態。

同時應該有一堆消費者線程等待,直到緩沖區中有東西,然后它會從緩沖區獲取內容,如果它是空的,則返回休眠狀態。

在偽代碼中,這是我正在做的事情,但所有我得到的都是死鎖。

condition_variable cvAdd;
condition_variable cvTake;
mutex smtx;

ProducerThread(){
    while(has something to produce){

         unique_lock<mutex> lock(smtx);
         while(buffer is full){
            cvAdd.wait(lock);
         }
         AddStuffToBuffer();
         cvTake.notify_one();
    }
}

ConsumerThread(){

     while(should be taking data){

        unique_lock<mutex> lock(smtx);
        while( buffer is empty ){
            cvTake.wait(lock);
        }   
        TakeStuffFromBuffer();
        if(BufferIsEmpty)
        cvAdd.notify_one();
     }

}

您的生產者和消費者都試圖鎖定互斥鎖,但兩個線程都沒有解鎖互斥鎖。 這意味着獲取鎖的第一個線程保持它,而另一個線程永遠不會運行。

考慮移動互斥鎖定調用,直到每個線程執行其操作,然后在每個線程執行其操作(AddStuffTobuffer()或TakeStuffFromBuffer())之后解鎖。

值得一提的另一個錯誤是,當緩沖區變空時,您的消費者只會通知等待的生產者。

僅在隊列已滿時才通知消費者的最佳方式。

例如:

template<class T, size_t MaxQueueSize>
class Queue
{
    std::condition_variable consumer_, producer_;
    std::mutex mutex_;
    using unique_lock = std::unique_lock<std::mutex>;

    std::queue<T> queue_;

public:
    template<class U>
    void push_back(U&& item) {
        unique_lock lock(mutex_);
        while(MaxQueueSize == queue_.size())
            producer_.wait(lock);
        queue_.push(std::forward<U>(item));
        consumer_.notify_one();
    }

    T pop_front() {
        unique_lock lock(mutex_);
        while(queue_.empty())
            consumer_.wait(lock);
        auto full = MaxQueueSize == queue_.size();
        auto item = queue_.front();
        queue_.pop();
        if(full)
            producer_.notify_all();
        return item;
    }
};

根據您的查詢查看此示例。 在這種情況下,單個condition_variable就足夠了。

#include "conio.h"
#include <thread>
#include <mutex>
#include <queue>
#include <chrono>
#include <iostream>
#include <condition_variable>

using namespace std;

mutex smtx;
condition_variable cvAdd;
bool running ;
queue<int> buffer;

void ProducerThread(){
    static int data = 0;
    while(running){
        unique_lock<mutex> lock(smtx);
        if( !running) return;
        buffer.push(data++);
        lock.unlock();
        cvAdd.notify_one();
        this_thread::sleep_for(chrono::milliseconds(300));
    }
}

void ConsumerThread(){

     while(running){

        unique_lock<mutex> lock(smtx);
        cvAdd.wait(lock,[](){ return !running || !buffer.empty(); });
         if( !running) return;
        while( !buffer.empty() )
        {
            auto data = buffer.front();
            buffer.pop();
            cout << data <<" \n";

            this_thread::sleep_for(chrono::milliseconds(300)); 
        }                

     }

}

int main()
{
    running = true;
    thread producer = thread([](){ ProducerThread(); }); 
    thread consumer = thread([](){ ConsumerThread(); });

    while(!getch())
    { }    

    running = false;
    producer.join();
    consumer.join();  
}

我以前回答過這個問題,但我有點偏離主題,因為我現在已經掌握了mutexlock_guard等的基本機制和行為。我一直在觀看關於這個主題的一些視頻和一個視頻我目前正在觀看的實際上與locking相反,因為視頻顯示了如何實現使用循環緩沖區或環形緩沖區,兩個指針以及使用atomic而不是mutexLockFreeQueue 現在,對於你目前的情況, atomic和一個LockFreeQueue將不能回答你的問題,但我從那個視頻中獲得的是循環緩沖區的想法。

由於兩個生產者/消費者線程將共享相同的內存池。 如果你有一個1比1的生產者 - 消費者線程,很容易跟蹤每個索引到數組或每個指針。 但是,當你有很多人時,事情往往會變得有點復雜。

可以做的一件事是,如果將緩沖區的大小限制為N個對象,實際上可能需要將其創建為N + 1。 一個額外的空白空間,有助於減輕多個生產者和消費者共享的環形緩沖區結構中的一些復雜性。


請看下面的插圖:

p =生產者指數,c =消費者指數,N代表[]指數空間數量。 N = 5。

一對一

 p                N = 5
[ ][ ][ ][ ][ ]
 c

這里p和c == 0.這表示緩沖區為空。 假設生產者在c收到任何東西之前填充緩沖區

             p    N = 5
[x][x][x][x][x]
 c

在這種情況下,緩沖區已滿,p必須等待空白區域。 c現在能夠獲得。

             p     N = 5
[ ][x][x][x][x]
    c         

這里c獲取了[0]處的對象,並將其索引提前為1 P現在能夠環繞環形緩沖區。

通過單一的p&c很容易跟蹤。 現在讓我們探討多個消費者和一個生產者

一對多

 p                 N = 5
[ ][ ][ ][ ][ ]
c1
c2

這里p index = 0,c1&c2 index = 0,環形緩沖區為空

             p     N = 5
[x][x][x][x][x]
c1
c2

現在p必須等待c1或c2才能在[0]處獲取項目,然后才能寫入

             p     N = 5
[ ][ ][x][x][x]
    c1 c2

如果c1或c2獲得[0]或1 ,但兩者都成功獲得了一個項目,這一點並不明顯。 兩者都增加了索引計數器。 以上似乎表明c1從[0]增加到1 然后在[0]處的c2也必須遞增索引計數器,但它已經從0變為1,因此c2將其遞增為2。

如果我們假設當p == 0 && c1 || c2 == 0時,這里存在死鎖情況 p == 0 && c1 || c2 == 0表示緩沖區為空。 看看這種情況。

 p               N = 5  // P hasn't written yet but has advanced 
[ ][ ][ ][ ][x]  // 1 Item is left
           c1  // Both c1 & c2 have index to same item.
           c2  // c1 acquires it and so does c2 but one of them finishes first and then increments the counter. Now the buffer is empty and looks like this:

 p                N = 5
[ ][ ][ ][ ][ ]
c1          c2    // p index = 0 and c1 = 0 represents empty buffer.
                  // c2 is trying to read [4]

這可能導致死鎖。

多對一

 p1
 p2                N = 5
[ ][ ][ ][ ][ ]
 c1

在這里,您有多個生成器可以寫入緩沖區以供單個使用者使用。 如果它們交錯:

p1 writes to [0] increments counter
p2 writes to [0] increments counter

   p1 p2
[x][ ][ ][ ][ ]
c1

這將導致緩沖區中出現空白區域。 生產者互相干擾。 你需要在這里互相排斥。

隨着許多人的想法; 你需要考慮並結合上述一對多和多對一的特征。 您需要為您的消費者使用互斥鎖,為您的生產者使用互斥鎖,嘗試使用相同的互斥鎖將為您提供可能導致無法預料的死鎖的問題。 您必須確保檢查所有案例,並確保在適當的時間鎖定它們 - 地點。 也許這幾個視頻可以幫助您了解更多。


偽代碼:可能看起來像這樣:

condition_variable cvAdd;
condition_variable cvTake;
mutex consumerMutex;
mutex producerMutex;

ProducerThread(){
    while( has something to produce ) {    
         unique_lock<mutex> lock(producerMutex);
         while(buffer is full){
            cvAdd.wait(lock);
         }
         AddStuffToBuffer();
         cvTake.notify_one();
    }
}

ConsumerThread() {    
     while( should be taking data ) {    
        unique_lock<mutex> lock(consumerMutex);
        while( buffer is empty ){
            cvTake.wait(lock);
        }   
        TakeStuffFromBuffer();
        if(BufferIsEmpty)
        cvAdd.notify_one();
     }    
}

這里唯一的區別是正在使用2個獨占的互斥鎖,而不是生產者和消費者試圖使用相同的互斥鎖。 正是共享的記憶; 但是你不想將計數器或指針共享到兩者之間的內存池中。 多個生產者可以使用相同的互斥鎖,並且多個消費者可以使用相同的互斥鎖,但讓消費者和生產者使用相同的互斥鎖可能是您的根本問題。

暫無
暫無

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

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