否,除了交换和移动具有互斥锁的类

noexcept swap and move for classes with mutexes

本文关键字:移动 交换      更新时间:2023-10-16

一般来说,声明交换和移动NO是一个很好的做法,因为这可以提供一些异常保证。 同时,编写线程安全类通常意味着添加互斥锁,保护内部资源免受争用的影响。 如果我想为这样的类实现交换函数,简单的解决方案是以安全的方式锁定交换的两个参数的资源,然后执行资源交换,例如,在回答这个问题时明确回答:使用 std::mutex 实现类的交换。

这种算法的问题在于互斥锁不是noexcept,因此swap不能,严格来说,noexcept。有没有解决方案可以安全地用互斥锁交换类的两个对象?

我想到的唯一可能性是将资源存储为句柄,以便交换成为可以通过原子方式完成的简单指针交换。 否则,人们可以将锁定异常视为不可恢复的错误,无论如何都应该终止程序,但这种解决方案感觉只是一种将灰尘放在地毯下的方法。

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

是否有可靠的实践来限制互斥锁在实际上是不可恢复的操作系统问题时可能抛给那些的情况? 我想到的是,在交换算法中,检查要交换的两个对象是否不同。这是一个明显的死锁情况,在最佳情况下会触发异常,但可以轻松检查。 是否有其他类似的触发器可以安全地检查以使交换功能健壮并且实际上没有除了所有重要情况之外?

在 POSIX 系统上,std::mutex通常是围绕pthread_mutex_t的薄包装器,在以下情况下,锁定和解锁功能可能会失败:

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

以上两者都是C++的UB,甚至不能保证POSIX会退货。在 Windows 上,如果std::mutexSRWLOCK的包装器,则两者都是 UB。

因此,似乎允许抛出lockunlock函数的要点是发出程序中错误的信号,而不是让程序员期望并处理它们。

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

该标准还提到这一点:

成员报告的错误代码(如果有(的错误条件 互斥锁类型的功能应为:

(4.1( —resource_unavailable_try_again— 如果有任何本机句柄类型 "已操纵"不可用。

(4.2( —operation_not_permitted— 如果线程没有 执行操作的权限。

(4.3( —invalid_argument— 如果任何本机句柄类型作为 部分互斥体构造不正确

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

因此,除非您在程序中导致与std::mutex使用相关的 UB,或者在某些特定于操作系统的场景中使用互斥锁,否则lockunlock的质量实现永远不应该抛出。

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