使用
mutex
锁定代码关键区域的推荐方法是通过 RAII,即
mutex_type mutex;
{ // start of critical region
std::lock_guard<mutex_type> lock(mutex); // first statement in critical region
// ... do critical stuff, may throw an exception
} // end of critical region
这样,当临界区内抛出异常时,互斥体仍将被解锁(通过
std::lock_guard
的析构函数)。然而,这样成员 mutex::lock()
和 mutex::unlock()
永远不会被用户代码显式调用。
Q
mutex::lock()
的主要惯用显式用法是什么(如果有)?
我是在问,否则让
mutex::lock()
一个公共成员推广不良代码(避免 std::lock_guard
)是没有意义的。
编辑 由于
std::lock_guard<>
和 std::mutex
都定义在同一个标头中,因此 std::mutex
可以轻松地与 std::lock_guard<std::mutex
成为朋友,并保护其 lock()
和 unlock()
方法:
class mutex // use only with lock_guard<mutex>
{
friend class lock_guard<mutex>; // support acquire-release semantic via RAII
friend class scoped_lock_guard<mutex>; // for supporting more complicated semantic,
// possibly remembering the mutex state.
// ...
protected:
void lock();
bool try_lock();
void unlock();
};
class raw_mutex // use if you absolutely must explicitly lock, try_lock, or unlock
: public mutex
{
public:
using mutex::lock;
using mutex::try_lock;
using mutex::unlock;
};
回答我的问题的一个论点很简单,使用
mutex::lock()
的唯一异常安全方法是通过 RAII。因此,唯一合理的显式使用必须在调用 noexcept
(或 lock
)和 try_lock
之间仅涉及 unlock
方法。然而,由于 noexcept
只是建议性的,不具有任何承诺,因此这种使用是不安全的。 问正确吗?
lock_guard
并不是唯一需要在 lock
上调用 unlock
/mutex
的事情。 unique_lock
、lock
、try_lock
和 condition_variable_any
都必须在互斥锁上工作。这只是标准类型。在这种情况下,友谊会带来紧密的耦合,从而成为一种障碍。
当存在可以绑定资源生命周期的静态作用域时,可以(并且应该)使用 RAII,即,应在进入作用域时初始化资源,并在退出作用域时释放资源。
但是,在某些情况下,不存在这样的静态范围。 为了说明这一点,将变量视为资源。当存在可以将它们绑定到的静态作用域时,我们使用自动变量,但有时我们需要动态变量来控制它们的创建和销毁时间。
当我们使用
mutex
进行互斥时,也会发生同样的情况。考虑这个任务:
您的程序控制两个资源,它必须读取并执行一系列命令。可能的命令是:
1>
2>
1<
2<
x
写入资源 #1 : 1x
x
写入资源 #2 : 2x
您的程序可以期待类似的输入
1>
1a
2>
2b
1c
1<
2d
2<
,
如果程序遵守命令,则无法使用 RAII 的静态作用域(作用域必须部分重叠)。
所以我认为这说明了一种需要显式锁定/解锁的情况。
至少还有另一种可能的情况,这是因为互斥并不是可以使用
mutex
的唯一情况:它也可以用于同步。
考虑两个并行进程,
P
和Q
,每个进程在其代码中都有一个指定点。我们要求 Q
不能通过其指定点,直到 P
到达自己的指定点。可以通过使用初始化为 mutex
状态的 locked
并在 lock()
指定点之前放置 Q
操作以及在 unlock()
指定点之后放置 P
来满足此要求。可以很容易地验证此设置解决了问题。
这里
lock()
被放置在一个进程中,而 unlock()
被放置在另一个进程中,同样显然没有静态作用域可以包含它们,所以我们需要两者都可以独立访问。