难道这段代码不会导致死锁吗?

时间:2015-07-03 10:52:05

标签: c++ c++11 deadlock

我有一个包含互斥锁和对象的类,每次我需要访问包含的对象时,调用一个方法来锁定互斥锁并返回包含的对象,让我们看看代码:

template <typename MUTEX, typename RESOURCE>
class LockedResource
{
    using mutex_t    = MUTEX;
    using resource_t = RESOURCE;

    mutex_t    m_mutex;
    resource_t m_resource;

public:
        template <typename ... ARGS>
        LockedResource(ARGS &&... args) :
                m_resource(std::forward<ARGS>(args) ...)
        {}

    class Handler
    {
        std::unique_lock<mutex_t> m_lock;      // unique lock
        resource_t                &m_resource; // Ref to resource

        friend class LockedResource;

        Handler(mutex_t &a_mutex, resource_t &a_resource) :
            m_lock(a_mutex),       // mutex automatically locked
            m_resource(a_resource)
        { std::cout << "Resource locked\n"; }

    public:
        Handler(Handler &&a_handler) :
            m_lock(std::move(a_handler.m_lock)),
            m_resource(a_handler.m_resource)
        { std::cout << "Moved\n"; }

        ~Handler() // mutex automatically unlocked
        { std::cout << "Resource unlocked\n"; }

        RESOURCE *operator->()
        { return &m_resource; }
    };

    Handler get()
    { return {m_mutex, m_resource}; }
};

template <typename T> using Resource = LockedResource<std::mutex, T>;

这段代码背后的想法是包装一个对象并保护它免受来自多个线程的多次访问;包装对象具有私有可见性,访问它的唯一方法是通过内部类Handler,预期用法如下:

LockedResource<std::mutex, Foo> locked_foo;
void f()
{
    auto handler = locked_foo.get(); // this will lock the locked_foo.m_mutex;
    handler->some_foo_method();
    // going out of the scope will call the handler dtor and
    // unlock the locked_foo.m_mutex;
}

因此,如果我没有弄错的话,调用LockedResource::get方法会创建一个LockedResource::Handle值,该值会在LockedResource::m_mutex的整个生命周期内锁定Handle。 ..但我必须弄错,因为下面的代码不会造成死锁:

LockedResource<std::mutex, std::vector<int>> locked_vector{10, 10};

int main()
{
/*1*/  auto vec = locked_vector.get(); // vec = Resource<vector>::Handler
/*2*/  std::cout << locked_vector.get()->size() << '\n';
/*3*/  std::cout << vec->size() << '\n';
    return 0;
}

我希望行/*1*/锁定locked_vector.m_mutex,然后行/*2*/尝试锁定同一个已锁定的互斥锁导致死锁,但输出如下:

Resource locked
Resource locked
10
Resource unlocked
10
Resource unlocked
  • 第二个::get()是否应该导致死锁?
  • 我通过同一个锁访问包装的资源,或者我误解了什么?

这是 example code

2 个答案:

答案 0 :(得分:4)

嗯,快速测试显示以下内容:

  1. GCC - 显示问题中显示的输出
  2. Clang - 在我使用的在线编译器上杀死了进程。死锁。
  3. MSVC2013 - “设备或资源繁忙:设备或资源繁忙” - 被抛出。它检测到尝试将已锁定的互斥锁锁定在同一个线程上。
  4. 有什么标准可以说呢?

      

    30.4.1.2.1 / 4 [注意:如果拥有互斥对象的线程在该对象上调用lock(),则程序可能死锁。如果执行   可以检测到死锁,可以观察到resource_deadlock_would_occur错误条件 。 - 结束说明]

    但根据30.4.1.2/13,它应该抛出其中一个:

    — resource_deadlock_would_occur — if the implementation detects that a deadlock would occur. 
    — device_or_resource_busy — if the mutex is already locked and blocking is not possible.
    

    所以答案是肯定的,你观察到的是不正确的行为。它应该阻止或抛出但不能继续,因为没有发生任何事情。

    由于代码中有UB,因此可以观察到的行为。根据17.6.4.11,违反 Requires 子句是UB,在30.4.1.2/7中我们有以下要求:

      

    需要:如果m的类型为std :: mutex,std :: timed_mutex,或者   std :: shared_timed_mutex,调用线程不拥有互斥锁。

    感谢@ T.C.指出UB。

答案 1 :(得分:0)

我不熟悉这个特定的互斥/资源实现,但这种同步原语包含LOCK COUNT并允许同一个线程锁定是很常见的相同的对象倍数倍。

当互斥锁解锁的次数与锁定次数相同时,另一个线程可以自由锁定它。