这段代码不应该导致死锁吗?
Shouldn't this code lead to a deadlock?
我有一个 class 包含一个互斥锁和一个对象,每次我需要访问包含的对象时,都会调用一个方法来锁定互斥锁和 return 包含的对象,让我们看代码:
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>;
此代码背后的想法是包装一个对象并保护它免受多个线程的多次访问;包装对象具有私有可见性,访问它的唯一方法是通过内部 class 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
值,该值会在 Handle
的整个生命周期内锁定 LockedResource::m_mutex
。 .. 但我一定是弄错了,因为下面的代码不会导致死锁:
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.
我不熟悉这个特定的 mutex/resource实现,但是这样的同步原语通常包含一个锁计数,并允许同一个线程多次锁定同一个对象。
当互斥体被解锁的次数与它被锁定的次数相同时,另一个线程就可以锁定它了。
嗯,快速测试显示如下:
- GCC - 显示问题中显示的输出
- Clang - 进程在我使用的在线编译器上终止。所以陷入僵局。
- MSVC2013 - "device or resource busy: device or resource busy" - 被抛出。它检测到试图在同一线程上锁定已锁定的互斥体。
关于它的标准是什么?
30.4.1.2.1/4 [ Note: A program may deadlock if the thread that owns a mutex object calls lock() on that object. If the implementation
can detect the deadlock, a resource_deadlock_would_occur error condition may be observed. — end note ]
但是根据 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 中我们有以下要求:
Requires: If m is of type std::mutex, std::timed_mutex, or
std::shared_timed_mutex, the calling thread does not own the mutex.
感谢@T.C。指出有关 UB 的信息。
我有一个 class 包含一个互斥锁和一个对象,每次我需要访问包含的对象时,都会调用一个方法来锁定互斥锁和 return 包含的对象,让我们看代码:
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>;
此代码背后的想法是包装一个对象并保护它免受多个线程的多次访问;包装对象具有私有可见性,访问它的唯一方法是通过内部 class 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
值,该值会在 Handle
的整个生命周期内锁定 LockedResource::m_mutex
。 .. 但我一定是弄错了,因为下面的代码不会导致死锁:
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.
我不熟悉这个特定的 mutex/resource实现,但是这样的同步原语通常包含一个锁计数,并允许同一个线程多次锁定同一个对象。
当互斥体被解锁的次数与它被锁定的次数相同时,另一个线程就可以锁定它了。
嗯,快速测试显示如下:
- GCC - 显示问题中显示的输出
- Clang - 进程在我使用的在线编译器上终止。所以陷入僵局。
- MSVC2013 - "device or resource busy: device or resource busy" - 被抛出。它检测到试图在同一线程上锁定已锁定的互斥体。
关于它的标准是什么?
30.4.1.2.1/4 [ Note: A program may deadlock if the thread that owns a mutex object calls lock() on that object. If the implementation can detect the deadlock, a resource_deadlock_would_occur error condition may be observed. — end note ]
但是根据 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 中我们有以下要求:
Requires: If m is of type std::mutex, std::timed_mutex, or std::shared_timed_mutex, the calling thread does not own the mutex.
感谢@T.C。指出有关 UB 的信息。