【问题标题】:std::timed_mutex::try_lock* fail spuriouslystd::timed_mutex::try_lock* 虚假失败
【发布时间】:2016-02-27 17:46:43
【问题描述】:
try_lock* 指的是try_lock()、try_lock_for() 和try_lock_until()。根据cppreference 的说法,所有三种方法都可能会虚假地失败。以下引自try_lock_for()的描述
与try_lock() 一样,此函数允许虚假失败,并且
返回false,即使互斥锁没有被任何其他线程锁定
timeout_duration 期间的某个时间点。
我知道std::condition_variable 可能会发生虚假唤醒及其背后的基本原理。但是,互斥锁是怎么回事?
【问题讨论】:
标签:
c++
multithreading
c++11
mutex
thread-synchronization
【解决方案1】:
来自 C++14 章节“30.4.1.2 互斥类型”
第 16 段:
即使没有被任何其他线程持有,实现也可能无法获得锁。 [注意:这种虚假故障通常不常见,但允许基于简单比较和交换的有趣实现(第 29 条)。 —end note] 实现应确保try_lock() 在没有竞争互斥收购的情况下不会始终返回false。
和第 19 段:
对于失败后的状态知之甚少,即使没有虚假失败
并回答
我知道 std::condition_variable 可能会发生虚假唤醒
及其背后的基本原理。但是,互斥锁是怎么回事?
std::timed_mutex 有时在操作系统没有直接支持时使用std::condition_varible 实现。与 GNU libstdc++ 一样:
#if _GTHREAD_USE_MUTEX_TIMEDLOCK
...
#else // !_GTHREAD_USE_MUTEX_TIMEDLOCK
class timed_mutex
{
mutex _M_mut;
condition_variable _M_cv;
bool _M_locked = false;
public:
template<typename _Rep, typename _Period>
bool
try_lock_for(const chrono::duration<_Rep, _Period>& __rtime)
{
unique_lock<mutex> __lk(_M_mut);
if (!_M_cv.wait_for(__lk, __rtime, [&]{ return !_M_locked; }))
return false;
_M_locked = true;
return true;
}
template<typename _Clock, typename _Duration>
bool
try_lock_until(const chrono::time_point<_Clock, _Duration>& __atime)
{
unique_lock<mutex> __lk(_M_mut);
if (!_M_cv.wait_until(__lk, __atime, [&]{ return !_M_locked; }))
return false;
_M_locked = true;
return true;
}
};
#endif
【解决方案2】:
根据:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3209.htm
另一方面,有充分的理由要求编写程序以容忍虚假的 try_lock() 失败:
- 正如 Boehm, Adve, “Foundations of the C++ Concurrency Memory Model”, PLDI 08 中所指出的,在没有虚假的 try_lock() 失败的情况下为无数据竞争的程序强制执行顺序一致性需要对 lock() 操作进行显着更强的内存排序在与 try_lock() 兼容的互斥体类型上。在一些显着增加非竞争互斥量获取成本的架构上。这一成本似乎大大超过了禁止虚假 try_lock() 失败所带来的任何好处。
- 它允许用户编写的 try_lock() 失败,例如,如果实现未能获取用于保护互斥数据结构的低级锁。或者它允许直接根据 compare_exchange_weak 编写这样的操作。
- 它确保客户端代码保持正确,例如,当引入调试线程时,偶尔会获取锁,以便能够从正在检查或检查的数据结构中读取一致的值。任何从 try_lock() 失败中获取信息的代码都会因引入另一个纯粹锁定和读取数据结构的线程而中断。