有没有人能指出为什么这段代码会导致死锁? 它是一个单一的生产者,多个消费者问题。制片人有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();
}
答案 0 :(得分:0)
这在很多方面存在缺陷。编译器可以重新排序指令,不同的CPU内核可能无法以相同的顺序查看内存操作。
基本上你的制作人这样做:
您的消费者会这样做:
由于多种原因,这不起作用。
这可能会导致各种麻烦 - 数据损坏,死锁,段错误,具体取决于您的代码究竟是什么。我还没有充分分析你的代码,告诉你为什么会导致死锁,但我并不感到惊讶。
请注意,&#39; volatile&#39;关键字对于此类同步完全没用。 &#39;挥发性&#39;仅对信号处理(unix信号)有用,而不适用于多线程代码。
执行此操作的正确方法是使用正确的同步(例如互斥)或原子操作(例如std :: atomic)。他们有各种不同的保证,以确保上述问题不会发生。
如果速度不是最重要的话,互斥锁通常更容易使用。原子操作可以让你获得更多的控制,但它们非常难以使用。
我建议您使用互斥锁执行此操作,然后对程序进行概要分析,然后仅在原子操作速度不够快的情况下进行原子操作。
valgrind是一个很好的工具,可用于调试多线程程序(它指出了不同步的内存访问等)。
答案 1 :(得分:0)
感谢您提供的有用评论。 我想如果确保从内存中读取所有标志/状态值,而不是从寄存器/缓存中读取,则无论编译器如何重新组织指令,都不应发生死锁。 volatile关键字应该强制执行此操作。看起来我的理解是错误的。
另一个令人困惑的事情是,我认为状态变量的值应该只是(0,1,2)中的一个,但是偶尔,我看到的值就像5384.不知怎的,数据被破坏了。