两个等待线程(生产者/消费者)与共享缓冲区

时间:2018-04-03 23:29:24

标签: c++ multithreading thread-safety producer-consumer

我试图让一堆生产者线程等待,直到缓冲区有一个项目的空间,然后它将项目放在缓冲区中,如果没有更多空间,则返回睡眠状态。

同时应该有一堆消费者线程等待,直到缓冲区中有东西,然后它会从缓冲区获取内容,如果它是空的,则返回休眠状态。

在伪代码中,这就是我正在做的事情,但所有我得到的都是死锁。

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();
     }

}

4 个答案:

答案 0 :(得分:1)

您的生产者和消费者都试图锁定互斥锁,但两个线程都没有解锁互斥锁。这意味着获取锁的第一个线程持有它而另一个线程永远不会运行。

考虑移动你的互斥锁定调用,直到每个线程执行其动作,然后在每个线程执行其动作(AddStuffTobuffer()或TakeStuffFromBuffer())之后解锁。

答案 1 :(得分:1)

另一个值得一提的错误是,当缓冲区变空时,您的使用者只会通知等待的生产者。

只有当队列已满时,才能通知消费者的最佳方式。

E.g:

query

答案 2 :(得分:0)

根据您的查询查看此示例。在这种情况下,单个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();  
}

答案 3 :(得分:-1)

我以前回答过这个问题,但我现在有点偏离主题,因为我现在已经掌握了mutexlock_guard等的基本机制和行为.I&#39我一直在观看有关该主题的一些视频,而我正在观看的视频实际上与locking相反,因为视频显示了如何实现使用循环缓冲区或环形缓冲区的LockFreeQueue ,两个指针,并使用atomic而不是mutex。现在,对于您目前的情况,atomicLockFreeQueue无法回答您的问题,但我从该视频中获得的是循环缓冲区的想法。

由于两个生产者/消费者线程将共享相同的内存池。如果你有一个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&amp; C。现在让我们探讨多个消费者和一个生产者

一对多

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

这里p index = 0,c1&amp; 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               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个独占的互斥锁,而不是生产者和消费者试图使用相同的互斥锁。正是共享的记忆;但是你不想在两者之间共享内存池中的计数器或指针。多个生产者可以使用相同的互斥锁,并且多个消费者可以使用相同的互斥锁,但让消费者和生产者使用相同的互斥锁可能是您的根本问题。