互斥锁和死锁

Mutexes and deadlocks

本文关键字:死锁      更新时间:2023-10-16

在这里问了一个关于互斥锁的问题之后,我被警告要注意死锁。

我下面列出的例子是避免死锁的合理方法吗?

class Foo
{
public:
    Foo();
    void Thread();
    int GetWidgetProperty();
    int GetGadgetProperty();
private:
    Widget widget_;
    Gadget gadget_;
    VDK::MutexID widgetLock;
    VDK::MutexID gadgetLock;
};

Foo::Foo()
    : widget_(42)
    , gadget_(widget_)
{
    widgetLock = VDK::CreateMutex();
    gadgetLock = VDK::CreateMutex();
}
void Foo::Thread()
{
    while(1)
    {
        VDK::AcquireMutex(widgetLock);
        // Use widget
        VDK::ReleaseMutex(widgetLock);
        VDK::AcquireMutex(widgetLock);
        VDK::AcquireMutex(gadgetLock);
        // use gadget
        VDK::ReleaseMutex(widgetLock);
        VDK::ReleaseMutex(gadgetLock);
    }
}
int Foo::GetWidgetProperty()
{
    VDK::AcquireMutex(widgetLock);
    return widget_.GetProp();
    VDK::ReleaseMutex(widgetLock);
}
int Foo::GetGadgetProperty()
{
    VDK::AcquireMutex(widgetLock);
    VDK::AcquireMutex(gadgetLock);
    return gadget.GetProp();
    VDK::ReleaseMutex(widgetLock);
    VDK::ReleaseMutex(gadgetLock);  
}

由于调用GetGadgetProperty可能导致使用小部件,我猜我们还需要在这里使用锁来保护我们的自我。我的问题是,我要求和释放它们的顺序是否正确?

您的代码有明显的死锁。不能在return语句后释放互斥锁。更重要的是,最好以相反的顺序解锁。正确的代码应该是这样的:

int Foo::GetWidgetProperty()
{
    VDK::AcquireMutex(widgetLock);
    int ret = widget_.GetProp();
    VDK::ReleaseMutex(widgetLock);
    return ret;
}
int Foo::GetGadgetProperty()
{
    VDK::AcquireMutex(widgetLock);
    VDK::AcquireMutex(gadgetLock);
    int ret = gadget.GetProp();
    VDK::ReleaseMutex(gadgetLock);  
    VDK::ReleaseMutex(widgetLock);
    return ret;
}

一个更好的方法是依靠RAII来为你做这项工作。

我邀请你阅读std::lock_guard。基本原则是通过声明一个对象来获取互斥锁。互斥锁在生命周期结束时自动释放。

现在,您可以对需要以这种方式锁定互斥锁的代码区域使用块作用域:

{
    std::lock_guard lockWidget{widgetMutex};//Acquire widget mutex
    std::lock_guard lockGadget{gadgetMutex};//Acquire gadget mutex
    //do stuff with widget/gadget
    //...
    //As the lock_guards go out of scope in the reverse order of 
    //declaration, the mutexes are released
}

当然,这适用于标准库的互斥体,因此您必须适应您的使用。

这将防止错误,例如试图在返回语句之后释放互斥锁,这显然是永远不会发生的,或者面对在实际释放互斥锁之前可能发生的异常。