std::mutex::lock可以抛出即使一切看起来"good"?

Could std::mutex::lock throw even if everything looks "good"?

本文关键字:看起来 good lock mutex std      更新时间:2023-10-16

在CPPReference中,没有明确表示如果锁不会导致死锁,std::mutex的锁函数就不会抛出。

PThread的锁只有一个死锁错误。我不知道windows的线程实现。我也不知道它们是否是用作std::thread/std::mutex后端的线程的其他实现。

因此,我的问题是"我是否应该在编写代码时,就好像在某些时候,由于没有特殊原因,锁可能会失败一样?"。

实际上,我需要在一些noexcept方法中锁定互斥对象,并且我想确保它们是noexcept。

std::mutex::lock()成员函数未声明为noexcept,且根据c++11标准(草案n3337)第6条:第30.4.1.2节的规定

表达式m.lock()应格式良好,并具有以下语义:

  • 当需要异常时抛出:system_error(30.2.2)
  • 错误条件:
    • operation_not_permitted——如果线程没有执行操作的权限
    • resource_deadlock_would_occur——如果实现检测到将发生死锁
    • device_or_resource_busy——如果互斥锁已经被锁定,并且阻塞是不可能的

这意味着任何使用mutex::lock()的函数都不能标记为noexcept,除非该函数本身能够处理异常并防止其向调用方传播。


我无法评论这些错误情况发生的可能性,但就std::mutexresource_deadlock_would_occur可能会抛出)而言,这表明代码中存在错误,而不是运行时失败,因为如果线程试图锁定其已拥有的std::mutex,则可能会引发此错误。来自30.4.1.2.1类互斥,第4条:

[注意:如果拥有互斥对象的线程对该对象调用lock(),则程序可能会死锁。如果实现能够检测到死锁,则可能会出现resource_deadlock_would_ecurse错误情况。--end Note]

通过选择std::mutex作为锁类型,程序员明确表示同一线程尝试锁定其已经锁定的mutex是不可能的。如果线程重新锁定mutex是合法的执行路径,则std:recursive_mutex是更合适的选择(但更改为recursive_lock并不意味着lock()函数没有异常)。

在POSIX系统上,std::mutex可能会使用POSIX互斥实现,而std::mutex::lock()最终会委托给pthread_mutex_lock()。尽管C++互斥体不需要使用POSIX互斥体来实现,但C++标准多线程的作者似乎已经根据POSIX错误条件对可能的错误条件进行了建模,因此检查这些条件可能很有指导意义。正如用户hmjd所说,lock方法允许的C++错误条件是operation_not_permittedresource_deadlock_would_occurdevice_or_resource_busy

POSIX错误条件为:

  • EINVAL:如果POSIX特定的锁优先级特性被误用,那么如果只使用标准的C++多线程功能,就永远不会发生这种情况。这种情况可能对应于operation_not_permitted C++错误代码
  • EINVAL:如果互斥对象尚未初始化,这将对应于损坏的std::mutex对象、悬挂引用的使用或其他指示程序错误的未定义行为
  • EAGAIN:如果互斥是递归的,并且递归太深。std::mutex不会发生这种情况,但std::recursive_mutex可能会发生这种情况。这似乎对应于device_or_resource_busy错误条件
  • EDEADLK:如果由于线程已经持有锁而导致死锁。这将对应于resource_deadlock_would_occurC++错误代码,但表示存在程序错误,因为程序不应试图锁定它已经锁定的std::mutex(如果您真的想这样做,请使用std::recursive_mutex

C++operation_not_permitted错误代码显然是为了对应POSIX EPERM错误状态。pthread_mutex_lock()函数从不提供此状态代码。但描述该功能的POSIX手册页面也描述了pthread_mutex_unlock()功能,如果您试图解锁未锁定的锁,该功能可能会给出EPERM。也许C++标准的作者由于错误地阅读了POSIX手册页面而包含了operation_not_permitted。由于C++没有锁"权限"的概念,很难看出任何正确构建和操作的锁(根据C++标准使用,不调用任何未定义的行为)如何会导致EPERM,从而导致operation_not_permitted

device_or_resource_busy在C++17中是不允许的,这表明它在实践中从未真正发生过,并且它在C++11中的包含是一个错误。

总之,std::mutex::lock()可能引发异常的唯一情况表明存在程序错误。因此,可以合理地假设方法"从不"抛出异常。

如果您能保证不存在任何错误条件(如hmjd的答案中所述),那么可以放心地假设互斥锁不会抛出。如何将该调用放入noexcept函数取决于您希望如何处理(非常不可能的)失败。如果noexcept(调用std::terminate)的默认值是可以接受的,那么您不需要做任何事情。如果您想记录不可能的错误,请将函数包装在try/catch子句中。