以下代码是整数的缓冲区。我试图通过多个线程(让我们说3个线程:1个“消费者”和2个“生产者”)找到可能导致死锁的代码:
class OneBuf{
Mutex m;
CondVar cv;
int buffer;
bool full;
void put (int data){
m.lock();
while(full) cv.wait(m);
buffer = data;
full = true;
cv.signal();
m.unlock();
}
int get(){
int data;
m.lock();
while(!full) cv.wait(m);
full = false;
data = buffer;
cv.signal();
m.unlock();
return data;
}
}
这是一个练习,我被要求给出一个导致死锁的3个线程(1个消费者和2个生产者)的例子。此外,练习声明如果我用cv.broadcast()
(而不是cv.signal()
)替换第12和22行,我可以避免任何死锁。我希望有所帮助。
答案 0 :(得分:0)
我认为你应该写一些类似的东西:
try {
mutex.acquire();
try {
// do something
} finally {
mutex.release();
}
} catch(InterruptedException ie) {
// ...
}
为了更准确地回答你的问题,我认为首先调用get()方法时会发生死锁。因此,线程锁定互斥锁并进入无限循环(因为条件永远不会成立)。
答案 1 :(得分:0)
好的,这解释了一些事情。我仍然无法看到如何解锁这段代码。如果我告诉我能做什么,也许会有所帮助。
通常需要broadcast(),因为signal()可能会错过一个线程,使其处于等待状态,直到另一个信号()最终唤醒它。虽然技术上不是死锁(有时它可以自行解决),但最好避免这种情况。
假设full不是bool,而是int,它有三种状态:true(1),false(0)和dumpable(-1),并且你有第三种方法dump():
void dump (void){
m.lock();
while(full!=-1) cv.wait(m);
cerr<<buffer<<endl;
full = 1;
cv.signal();
m.unlock();
}
和put()将full设置为1,但设置为-1(可转储)状态,以便执行的正常线性顺序为
buf.put(7);
buf.dump();
int val=buf.get();
然后假设多线程执行是这样的:
1)带有dump()的线程进入,发现full为0,进入等待状态。
2)一个带get()的线程进入,发现full为0,等待。
3)带有put()的线程进入,设置值,将full设置为-1,发送信号,退出解锁互斥锁。
4)get()赢得比赛,然后醒来。发现该值为-1而不是1,再次等待。它仍然解锁互斥锁,所以其他一些put()会再次发送信号,也许现在dump()会赢。或者可能不是。虽然不是僵局,但它很糟糕,并且很容易退化成几乎与真正的僵局无法区分的东西。
通过将signal()更改为broadcast()可以很容易地解决这个问题。如果put()发送broadcast(),则在4)中唤醒get()和dump()。他们仍在比赛,但这一次他们争夺互斥体。 get()仍然可以赢得比赛,但即使它确实如此,dump()也不会再等待(),它会一直停止,直到互斥锁被解锁,即直到get()检查完全!= 1然后去等待()解锁互斥锁。所以broadcast()确实修复了这个代码,每个现有的推杆都会找到一个转储器,最终会找到一个吸气剂。
这个例子是人为的,几乎是愚蠢的,但我希望它有助于说明通常的问题和解决方案。
回到您的代码,如果我们在等待池中同时获得getter和putter,则会发生这样的退出等待竞争。在这种情况下,get()可以很容易地从另一个get()的信号中唤醒,然后再次等待。信号将被遗漏,而丢失比赛的put()可以无限期地保持等待状态,就像在我的例子中一样。
但是如果游泳池只包含吸气剂或推杆,我看不出信号()是如何弄乱的:如果游泳池由吸气剂组成,任何其他吸气剂都会进入水池,任何推杆的信号都会找到一个醒来的吸气鬼。而我无法看到的是,吸气剂和推杆都可以用你的代码进入等待状态。即使比方说两个推杆竞争互斥锁,而两个吸气剂等待,赢家将唤醒一个吸气剂,这将保证继承其waker的互斥体。失败者推杆和从等待返回的吸气器之间没有比赛,等待的回归获胜。也许我想念一些东西。