【问题标题】:Could std::mutex::lock throw even if everything looks "good"?即使一切看起来“好”,std::mutex::lock 也会抛出吗?
【发布时间】:2013-07-07 05:43:33
【问题描述】:

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

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

所以我的问题是“我是否应该编写我的代码,就好像在某些时候,没有特殊原因,锁可能会失败?”。

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

【问题讨论】:

  • +1 可能会抛出一个锁,恕我直言,这真的不是一件好事。当然,我们需要在析构函数中调用它们。毕竟我最终写了my own mutex class

标签: c++ c++11 exception locking mutex


【解决方案1】:

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_occur 错误情况。 ——尾注]

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

【讨论】:

  • 我知道 resource_deadlock_would_occur 可以被抛出,但我可以(应该)防止这种情况发生,因为正如你所说,这意味着一个错误。所以它接缝我不能为另外两个做任何事情。由于中止不是一种选择,我将不得不处理它们:)
  • device_or_resource_busy 显然在 C++17 中是不允许的。这表明它在实践中从未真正发生过。
【解决方案2】:

在 POSIX 系统上,std::mutex 可能会使用 POSIX 互斥体实现,std::mutex::lock() 最终将委托给pthread_mutex_lock()。尽管 C++ 互斥锁不需要使用 POSIX 互斥锁来实现,但 C++ 标准多线程的作者似乎已经根据 POSIX 错误条件对可能的错误条件进行了建模,因此检查这些可能是有益的。作为user hmjd saysC++ error conditions permitted for the lock methodoperation_not_permittedresource_deadlock_would_occurdevice_or_resource_busy

POSIX 错误条件是:

  • EINVAL:如果滥用 POSIX 特定的锁定优先功能,如果您仅使用标准 C++ 多线程工具,则永远不会发生这种情况。这种情况可能对应operation_not_permittedC++错误码。
  • EINVAL:如果互斥锁尚未初始化,这将对应于损坏的std::mutex 对象、使用悬空引用或其他一些指示程序错误的未定义行为。
  • EAGAIN:如果互斥体是递归的并且递归太深。 std::mutex 不会发生这种情况,但std::recursive_mutex 可能会发生这种情况。这似乎对应于device_or_resource_busy 错误条件。
  • EDEADLK:如果因为线程已经持有锁而发生死锁。这将对应于 resource_deadlock_would_occur C++ 错误代码,但表示程序错误,因为程序不应尝试锁定 std::mutex 它已经持有锁定(如果您真的想这样做,请使用 std::recursive_mutex )。

C++ operation_not_permitted 错误代码是 evidently 旨在对应于 POSIX EPERM 错误状态。 pthread_mutex_lock() 函数 从不 提供此状态代码。但是描述该函数的 POSIX 手册页也描述了 pthread_mutex_unlock() 函数,如果你尝试解锁你没有锁定的锁,它可能会给出 EPERM。也许 C++ 标准的作者误读了 POSIX 手册页,将operation_not_permitted 包括在内。由于 C++ 没有锁“权限”的概念,因此很难看出任何正确构造和操作的锁(根据 C++ 标准使用,不调用任何未定义的行为)如何导致EPERMoperation_not_permitted

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

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

【讨论】:

  • 不幸的是 device_or_resource_busy 发生在 Visual Studio (2017) 编译代码中的一些神秘案例中,我用它。
【解决方案3】:

如果您可以保证不存在任何错误条件(如 hmjd 的回答中所述),则可以安全地假设互斥锁不会抛出。如何将该调用放入 noexcept 函数取决于您希望如何处理(几乎不可能的)故障。如果默认的 noexcept (调用std::terminate 是可以接受的,你不需要做任何事情。如果你想记录不可能的错误,将函数包装在 try/catch 子句中。

【讨论】:

    猜你喜欢
    • 2017-03-28
    • 2021-06-06
    • 2012-03-15
    • 1970-01-01
    • 1970-01-01
    • 2018-11-12
    • 1970-01-01
    • 2013-10-16
    • 1970-01-01
    相关资源
    最近更新 更多