单个生产者多个消费者案例中的死锁

时间:2015-07-23 22:40:35

标签: deadlock producer-consumer

有没有人能指出为什么这段代码会导致死锁? 它是一个单一的生产者,多个消费者问题。制片人有8个缓冲区。这里有4个消费者。每个消费者将有两个缓冲区。填充缓冲区时,它会将其标记为准备使用并切换到第二个缓冲区。然后,消费者可以处理此缓冲区。完成后,它将缓冲区返回给生产者。

消费者0的缓冲区0-1 消费者1的缓冲区2-3 消费者2的缓冲区4-5 消费者3的缓冲区6-7

程序一会儿达到死锁状态。 理解是,由于标志只能在一个状态,0或1,所以至少消费者或生产者可以继续。它继续进行,它最终将解锁死锁。

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

const int BUFFERSIZE = 100;
const int row_size = 10000;
class sharedBuffer
{
public:
    int B[8][BUFFERSIZE];
    volatile int B_STATUS[8];
    volatile int B_SIZE[8];
    sharedBuffer()
    {
        for (int i=0;i<8;i++)
        {
            B_STATUS[i] = 0;
            B_SIZE[i] = 0;
            for (int j=0;j<BUFFERSIZE;j++)
            {
                B[i][j] = 0;
            }
        }
    }

};

class producer
{
public:
    sharedBuffer * buffer;
    int data[row_size];
    producer(sharedBuffer * b)
    {
        this->buffer = b;
        for (int i=0;i<row_size;i++)
        {
            data[i] = i+1;
        }
    }
    void produce()
    {
        int consumer_id;
        for(int i=0;i<row_size;i++)
        {
            consumer_id = data[i] % 4;
            while(true)
            {
                if (buffer->B_STATUS[2*consumer_id] ==1 && buffer->B_STATUS[2*consumer_id + 1] == 1)
                continue;
                if (buffer->B_STATUS[2*consumer_id] ==0 )
                {
                    buffer->B[2*consumer_id][buffer->B_SIZE[2*consumer_id]++] = data[i];
                    if(buffer->B_SIZE[2*consumer_id] == BUFFERSIZE || i==row_size -1)
                    {
                        buffer->B_STATUS[2*consumer_id] =1;
                    }
                    break;  
                }
                else if (buffer->B_STATUS[2*consumer_id+1] ==0 )
                {
                    buffer->B[2*consumer_id+1][buffer->B_SIZE[2*consumer_id+1]++] = data[i];
                                        if(buffer->B_SIZE[2*consumer_id+1] == BUFFERSIZE || i==row_size -1)
                                        {
                                                buffer->B_STATUS[2*consumer_id+1] =1;
                                        }
                                        break;
                } 
            }       
        }
        //some buffer is not full, still need set the flag to 1
        for (int i=0;i<8;i++)
        {
            if (buffer->B_STATUS[i] ==0 && buffer->B_SIZE[i] >0 )
                buffer->B_STATUS[i] = 1;
        }
        cout<<"Done produce, wait the data to be consumed\n";
        while(true)
        {
            if (buffer->B_STATUS[0] == 0 && buffer->B_SIZE[0] == 0 
                && buffer->B_STATUS[1] == 0 && buffer->B_SIZE[1] == 0 
                && buffer->B_STATUS[2] == 0 && buffer->B_SIZE[2] == 0 
                && buffer->B_STATUS[3] == 0 && buffer->B_SIZE[3] == 0
                && buffer->B_STATUS[4] == 0 && buffer->B_SIZE[4] == 0 
                && buffer->B_STATUS[5] == 0 && buffer->B_SIZE[5] == 0 
                && buffer->B_STATUS[6] == 0 && buffer->B_SIZE[6] == 0 
                && buffer->B_STATUS[7] == 0 && buffer->B_SIZE[7] == 0 )             
            {
                for (int i=0;i<8;i++)
                    buffer->B_STATUS[i] = 2;
                break;
            }
        }       
    };

};

class consumer
{
public:
    sharedBuffer * buffer;
    int sum;
    int index;
    consumer(int id, sharedBuffer * buf){this->index = id;this->sum = 0;this->buffer = buf;};
    void consume()
    {
        while(true)
        {
            if (buffer->B_STATUS[2*index] ==0 && buffer->B_STATUS[2*index+1] ==0 )
                continue;
            if (buffer->B_STATUS[2*index] ==2 && buffer->B_STATUS[2*index+1] ==2 )
                                break;
            if (buffer->B_STATUS[2*index] == 1)
            {
                for (int i=0;i<buffer->B_SIZE[2*index];i++)
                {
                    sum+=buffer->B[2*index][i];
                }
                buffer->B_STATUS[2*index]=0;
                buffer->B_SIZE[2*index] =0; 
            }

            if (buffer->B_STATUS[2*index+1] == 1)
                        {
                                for (int i=0;i<buffer->B_SIZE[2*index+1];i++)
                                {
                                        sum+=buffer->B[2*index+1][i];
                                }
                                buffer->B_STATUS[2*index+1]=0;
                                buffer->B_SIZE[2*index+1] =0;
                        }

        }
        printf("Sum of consumer %d = %d \n",index,sum);
    };

};
int main()
{
    sharedBuffer b;
    producer p(&b);
    consumer c1(0,&b),c2(1,&b),c3(2,&b),c4(3,&b);
        thread p_t(&producer::produce,p);
    thread c1_t(&consumer::consume,c1);
    thread c2_t(&consumer::consume,c2);
    thread c3_t(&consumer::consume,c3);
    thread c4_t(&consumer::consume,c4);
    p_t.join();c1_t.join();c2_t.join();c3_t.join();c4_t.join();
}

2 个答案:

答案 0 :(得分:0)

这在很多方面存在缺陷。编译器可以重新排序指令,不同的CPU内核可能无法以相同的顺序查看内存操作。

基本上你的制作人这样做:

  1. 将数据写入缓冲区
  2. 设置标志
  3. 您的消费者会这样做:

    1. 它读取标志
    2. 如果该标志是它想要的,则它会读取数据
    3. 重置标志
    4. 由于多种原因,这不起作用。

      • 编译器可以重新排序您的指令(在消费者和生产者方面)以不同的顺序执行操作。例如,在生产者方面,它可以将所有计算存储在寄存器中,然后将状态标志首先写入内存,然后将数据写入。然后,消费者将获得过时的数据。
      • 即使没有这种情况,也不能保证不同的CPU核心以相同的顺序看到对内存的不同写入(例如,如果它们具有单独的高速缓存,并且您的标志和数据位于不同的高速缓存行上)。

      这可能会导致各种麻烦 - 数据损坏,死锁,段错误,具体取决于您的代码究竟是什么。我还没有充分分析你的代码,告诉你为什么会导致死锁,但我并不感到惊讶。

      请注意,&#39; volatile&#39;关键字对于此类同步完全没用。 &#39;挥发性&#39;仅对信号处理(unix信号)有用,而不适用于多线程代码。

      执行此操作的正确方法是使用正确的同步(例如互斥)或原子操作(例如std :: atomic)。他们有各种不同的保证,以确保上述问题不会发生。

      如果速度不是最重要的话,互斥锁通常更容易使用。原子操作可以让你获得更多的控制,但它们非常难以使用。

      我建议您使用互斥锁执行此操作,然后对程序进行概要分析,然后仅在原子操作速度不够快的情况下进行原子操作。

      valgrind是一个很好的工具,可用于调试多线程程序(它指出了不同步的内存访问等)。

答案 1 :(得分:0)

感谢您提供的有用评论。 我想如果确保从内存中读取所有标志/状态值,而不是从寄存器/缓存中读取,则无论编译器如何重新组织指令,都不应发生死锁。 volatile关键字应该强制执行此操作。看起来我的理解是错误的。

另一个令人困惑的事情是,我认为状态变量的值应该只是(0,1,2)中的一个,但是偶尔,我看到的值就像5384.不知怎的,数据被破坏了。