noexcept 使用互斥量交换和移动 类

noexcept swap and move for classes with mutexes

一般来说,声明一个交换并移动 noexcept 是一个很好的做法,因为这样可以提供一些异常保证。 同时编写一个线程安全的 class 通常意味着添加一个互斥锁来保护内部资源免受竞争。 如果我想为这样的 class 实现交换功能,直接的解决方案是以安全的方式锁定交换的两个参数的资源,然后执行资源交换,例如,在答案中明确回答对于这个问题: .

这种算法的问题在于互斥锁不是 noexcept,因此严格来说 swap 不能是 noexcept。是否有安全交换 class 的两个对象与互斥量的解决方案?

我想到的唯一可能性是将资源存储为句柄,以便交换成为可以自动完成的简单指针交换。 否则,人们可能会认为锁定异常是不可恢复的错误,无论如何都应该终止程序,但这个解决方案感觉就像是一种掩盖灰尘的方法。

编辑: 正如评论中所说,我知道互斥体抛出的异常不是 任意 但问题可以这样改写:

是否有稳健的实践来限制互斥量实际上是不可恢复的 OS 问题时可能抛出的情况? 我想到的是在交换算法中检查要交换的两个对象是否不相同。这是一个明显的死锁情况,在最好的情况下会触发异常,但可以轻松检查。 是否有其他类似的触发器,人们可以安全地检查这些触发器以使交换功能健壮并且几乎在所有重要情况下都没有?

在 POSIX 系统上,std::mutexpthread_mutex_t 的薄包装器是很常见的,在以下情况下,锁定和解锁功能可能会失败:

  • 正在尝试获取已拥有的锁
  • 互斥对象未初始化或已被销毁

以上都是C++中的UB,甚至不保证POSIX会返回。在 Windows 上,如果 std::mutexSRWLOCK.

的包装器,则两者都是 UB

所以看起来允许 lockunlock 函数抛出的主要目的是发出程序错误的信号,而不是让程序员预期并处理它们。

推荐的锁定模式证实了这一点:析构函数 ~unique_locknoexcept(true),但应该调用 unlock,即 noexcept(false)。这意味着如果 unlock 函数抛出异常,整个程序将被 std::terminate.

终止

标准中也提到了这一点:

The error conditions for error codes, if any, reported by member functions of the mutex types shall be:

(4.1) — resource_unavailable_try_again — if any native handle type manipulated is not available.

(4.2) — operation_not_permitted — if the thread does not have the privilege to perform the operation.

(4.3) — invalid_argument — if any native handle type manipulated as part of mutex construction is incorrect

理论上您可能会遇到 operation_not_permitted 错误,但标准中并未真正定义发生这种情况的情况。

因此,除非您在程序中导致与 std::mutex 用法相关的 UB 或在某些 OS 特定场景中使用互斥锁,否则 quality 实现lockunlock 不应该抛出。

在常见的实现中,至少有一个可能是低质量的:std::mutex 在旧版本 Windows 的 CRITICAL_SECTION 之上实现(我认为 Windows XP 及更早版本)在争用期间未能延迟分配内部事件后可能会抛出。另一方面,甚至更早的版本在初始化期间分配此事件以防止稍后失败,因此 std::mutex::mutex 构造函数可能需要在那里抛出(即使它在标准中是 noexcept(true))。