为啥 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_lockpthread_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::mutexnoexcept,但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 的构造函数不抛出?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 线程

使用移动语义在构造函数中初始化类成员

为啥使用 std::mutex 的函数对 pthread_key_create 的地址进行空检查?

std::mutex 锁定的顺序

是否有任何理由应该将 C++ 11+ std::mutex 声明为全局变量,而不是作为函数参数传递给 std::thread ?

C++并发编程之二 在线程间共享数据