为啥 C++ 中 std::mutex 的构造函数不抛出?
Posted
技术标签:
【中文标题】为啥 C++ 中 std::mutex 的构造函数不抛出?【英文标题】:Why the constructor of std::mutex in C++ does not throw?为什么 C++ 中 std::mutex 的构造函数不抛出? 【发布时间】:2018-02-09 02:51:12 【问题描述】:pthread_mutex_init()
函数在初始化互斥锁失败时返回一个非零值,而C++11中的std::mutex
类有noexcept
的构造函数。
假设一个人选择在 pthreads 互斥锁之上实现一个 C++ 互斥锁类。他在类中包装了一个 pthread 互斥锁,并尝试通过在构造函数中调用 pthread_mutex_init() 来初始化它。如果函数调用返回一个非零值,即错误,由于构造函数不能抛出,所以不能立即报告错误。一种替代方法是在互斥体上实际调用锁定方法之前抛出异常。但这种方法似乎是错误的。
还有其他方法可以做到这一点,采用一些巧妙的技巧来保证初始化互斥锁总是成功吗?
更新:我将在这个问题上回答我自己的问题。根据language standard,在 30.4.1.3 pge 1163 中,它说“。如果一个 mutex 类型的对象初始化失败,将抛出一个 system_error 类型的异常。”
并且noexcept的函数可以在函数体内抛出,只是调用者无法捕捉到异常。如果在 noexcept 函数中抛出异常,将调用 std::terminate。
【问题讨论】:
根据this document,“第1163页第30.4.1.2节,互斥类型的成员函数报告的错误代码的错误条件(如果有)应为:-resource_unavailable_try_again; operation_not_permitted ... device_or_reso 好问题,顺便说一句。 @Steve 我不明白。根据该文档,在 30.4.1.3 pge 1163 中,它说“。如果互斥锁类型的对象初始化失败,则应抛出 system_error 类型的异常。”但互斥锁构造函数不能抛出。 @JohnZ.Li:自己的问题自己回答就可以了,如果没有人给出更好的答案,你甚至可以接受。请但是,请不要将答案放在您的问题中。这使得这个问题看起来没有答案。 请注意,std::mutex
不要求必须使用POSIX线程实现,即pthread
库。
【参考方案1】:
std::mutex
的构造函数需要是constexpr
(这样一个全局的std::mutex
可以被静态初始化并在其他全局对象的构造函数中使用),因此不能调用pthread_mutex_init
(或类似函数)在全部。
相反,它需要使用PTHREAD_MUTEX_INITIALIZER
或等效项(例如,Windows 上的SRWLOCK_INIT
)来静态初始化互斥锁。
【讨论】:
我认为静态初始化的互斥锁和一般的互斥锁是不同的野兽。在前一种情况下,使用像 PTHREAD_MUTEX_INITIALIZER 这样的宏。【参考方案2】:在我看来,来自 pthread_mutex_init
的错误在 libstdc++ 中被简单地忽略了:
https://github.com/psp2sdk/libs/blob/master/include/c%2B%2B/bits/gthr-posix.h#L732
其中__gthread_mutex_init_function
是通过宏__GTHREAD_MUTEX_INIT_FUNCTION
在此处调用的
https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/std_mutex.h#L75
通过其基类在std::mutex
构造函数中。
更新
可以用PTHREAD_MUTEX_INITIALIZER
初始化Pthread互斥锁,然后
不执行错误检查
我猜错误处理可以推迟到锁定函数;引用 pthread_mutex_lock
和 pthread_mutex_trylock
ERRORS 部分的文档:
EINVAL
mutex 指定的值不引用已初始化的 mutex 对象。
这意味着pthread_mutex_init
中的错误可以在std::mutex
构造函数中安全地忽略。
【讨论】:
我需要检查一下这个。如何安全忽略 pthread_mutex_init 中的错误。【参考方案3】:根据 C++17 规范:
33.4.3.2 互斥类型 [thread.mutex.requirements.mutex]
互斥体类型应为 DefaultConstructible 和 Destructible。如果互斥锁类型的对象初始化失败,则应抛出 system_error 类型的异常。互斥锁类型不得复制或移动。
所以mutex type
可能会抛出不是std::mutex
的异常。 std::mutex
有noexcept
,但std::recursive_mutex
没有,它们都是mutex types
:
33.4.3.2.1 类互斥体 [thread.mutex.class]
constexpr mutex() noexcept;
33.4.3.2.2 类 recursive_mutex [thread.mutex.recursive]
recursive_mutex();
此外:
20.5.5.12 异常处理限制 [res.on.exception.handling]
C++ 标准库中定义的任何函数都可以通过抛出其 Throws: 段落中描述的类型或派生自 Throws: 段落中命名的类型的异常来报告失败,该类型将被捕获基类型的异常处理程序。
在 C++ 标准库中定义的没有 Throws: 段落但有可能引发异常规范的函数可能会引发实现定义的异常。实现应通过抛出标准异常类(21.6.3.1、21.8、22.2)的异常或派生自标准异常类来报告错误。
没有Throws段落,也没有潜在的抛出异常规范。
所以std::mutex
构造函数永远不会抛出异常或调用std::terminate
,就像标准库中的任何其他函数一样,noexcept
规范符合 C++ 实现。
【讨论】:
【参考方案4】:如果你总结一下,这会导致pthread_mutex_init
不能在std::mutex
构造函数中调用。构造/初始化不需要一对一的映射。恰恰相反!
【讨论】:
以上是关于为啥 C++ 中 std::mutex 的构造函数不抛出?的主要内容,如果未能解决你的问题,请参考以下文章
为啥使用 std::mutex 的函数对 pthread_key_create 的地址进行空检查?
是否有任何理由应该将 C++ 11+ std::mutex 声明为全局变量,而不是作为函数参数传递给 std::thread ?