【问题标题】:How can this source code can create deadlock?这个源代码怎么会造成死锁?
【发布时间】:2014-01-24 20:12:25
【问题描述】:

以下代码是一个整数的缓冲区。我试图找到可能导致死锁的多个线程(假设 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 个生产者)的示例。此外,练习表明,如果我将第 12 行和第 22 行替换为 cv.broadcast()(而不是 cv.signal()),我可以避免任何死锁。我希望这会有所帮助。

【问题讨论】:

  • 看不到任何明显的死锁该代码的方法(假设 Mutex 和 CondVar 被实现为相应 pthread 调用的 straitforward 包装)。你看到它实际上陷入僵局了吗?
  • @MikeTyukanov 请阅读我在代码下方添加的内容。我希望它有所帮助。

标签: c++ multithreading buffer mutex deadlock


【解决方案1】:

好的,这说明了一些事情。我仍然看不出如何使这段代码死锁。如果我能说出来,也许会有所帮助。

broadcast() 经常需要,因为 signal() 可能会错过一个线程,使其处于等待状态,直到另一个 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() 也不会再次进入 wait(),它只会停止直到互斥锁被解锁,即直到 get() 检查 full!=1 并继续等待()解锁互斥锁。所以broadcast()确实修复了这段代码,每个退出的推杆都会找到一个dumper,最终会找到一个getter。

这个例子是人为的,几乎是愚蠢的,但我希望它有助于说明常见的问题和解决方案。

回到您的代码,如果我们在等待池中同时获得 getter 和 putter,则可能会发生这种退出等待竞赛。在这种情况下,get() 可以轻松地从另一个 get() 的信号中唤醒,然后再次等待。信号将丢失,而输掉比赛的 put() 可以无限期地保持等待状态,就像在我的示例中一样。

但是如果池仅由 getter 或 putter 组成,我看不到 signal() 会如何搞砸事情:如果池由 getter 组成,任何其他 getter 将进入池,并且任何推杆的信号都会找到唤醒的吸气剂。而我看不到的是 getter 和 putter 如何通过您的代码进入等待状态。即使,例如,两个推杆争夺互斥锁,而两个 getter 等待,获胜者将唤醒一个 getter,该 getter 将保证继承其唤醒器的互斥锁。失败的推杆和从等待返回的吸气剂之间没有比赛,从等待返回的获胜。也许我错过了什么。

【讨论】:

    【解决方案2】:

    我认为你应该写这样的东西:

    try {
      mutex.acquire();
      try {
        // do something
      } finally {
        mutex.release();
      }
    } catch(InterruptedException ie) {
      // ...
    }
    

    为了更准确地回答您的问题,我认为首先调用 get() 方法可能会发生死锁。因此,线程锁定互斥体并进入无限循环(因为条件永远不会为真)。

    【讨论】:

    • 在循环内部,对 cv.wait(m) 的调用会解锁 m 并暂停线程,直到有人调用 cv.signal()。如果另一个线程调用另一个 get(),它也会转到 wait()。但是如果另一个线程调用put(),它会避免循环,并且赋值buffer=value;全=真;在 put() 调用 signal() 并解锁互斥锁以允许 getter 线程中的 wait() 完成之前。因此,在下一次检查中,条件将为真。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-27
    • 1970-01-01
    • 2017-11-16
    • 1970-01-01
    相关资源
    最近更新 更多