在 C++ 中销毁 pthread 互斥锁和去初始化顺序
Destruction of pthread mutexes and deinitialization order in C++
pthread 互斥锁在程序生命周期结束前一直存在似乎很常见。通常这些是使用 PTHREAD_MUTEX_INITIALIZER
.
创建的
这是一个简短但完整的代码示例,显示了我所指的内容:
#include <pthread.h>
#include <iostream>
void log(char const * const message) {
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
std::cout << message << std::endl;
pthread_mutex_unlock(&mutex);
}
struct Object final {
Object() { log("Object::Object()"); }
~Object() { log("Object::~Object()"); }
};
Object const object;
int main(int const argc, const char * argv[]) {
log("main()");
// Here the program would enter a main loop, with log() potentially being
// called from multiple threads at various times.
}
输出:
Object::Object()
main()
Object::~Object()
为简洁起见,省略了锁的错误检查和 RAII 包装。
这个例子包括一个线程安全的(至少是有意的)日志记录功能,该功能旨在在程序的整个生命周期内可用,包括在具有静态存储持续时间的对象的去初始化期间,可能(尽管在这种情况下不是) 跨多个翻译单元,这意味着取消初始化顺序可能是不确定的。
问题是没有机会安全地销毁互斥量,因为在程序生命周期的任何时候都可能需要它。 (在实践中,我不打算拥有具有静态存储持续时间和非平凡析构函数的对象,但我仍然有兴趣解决这个问题。)
出现的第一个问题是使用PTHREAD_MUTEX_INITIALIZER
初始化的互斥量是否需要使用pthread_mutex_destroy()
销毁。至少 documentation 的某些版本包含以下措辞:
In cases where default mutex attributes are appropriate, the macro
PTHREAD_MUTEX_INITIALIZER can be used to initialize mutexes. The
effect shall be equivalent to dynamic initialization by a call to
pthread_mutex_init() with parameter attr specified as NULL, except
that no error checks are performed.
这表明如果 pthread_mutex_destroy()
预期在使用 pthread_mutex_init()
初始化的互斥体上调用,那么它也应该在使用 PTHREAD_MUTEX_INITIALIZER
初始化的互斥体上调用。
但是,在网上和 Stack Overflow 上搜索时,我发现对于是否需要它存在分歧。例如,here 某人引用了一本关于 Linux 开发的书:
It is not necessary to call pthread_mutex_destroy() on a mutex that
was statically initialized using PTHREAD_MUTEX_INITIALIZER.
另一方面,在 this thread 中,有人认为明确销毁这样的互斥体实际上是必需的。
我还看到它争辩说在这种情况下没有必要清理互斥体,不管它们是如何初始化的,因为资源无论如何都会被回收。 (这可能与 'construct on first use and deliberately leak memory' 有时用于单例和其他具有静态存储持续时间的对象的习惯用法背后的逻辑相同。)
我发现了一些涉及该主题的其他线程,其中混合了关于 if/how 互斥体应该被销毁的意见。我还要提到的是,我相信我已经看到来自可靠来源的生产代码,这些代码使用 PTHREAD_MUTEX_INITIALIZER
初始化互斥量并且从不销毁它们。
出于尽职调查的考虑,我在这里详细介绍了一些细节,但我的问题(我认为)相当简单。拥有从初始化到程序生命周期结束的互斥量是很有用的。我怀疑不清理此类互斥锁不会造成任何问题,但这种方法让我很烦恼。尽管有人说不需要清理使用 PTHREAD_MUTEX_INITIALIZER
初始化的互斥体,但这似乎与文档和其他人提出的各种说法相悖。
总而言之,是否有一种安全合理的方法来管理旨在在程序生命周期结束之前可用的 pthread 互斥量?这里有没有我在搜索中偶然发现的标准最佳实践?
由于用PTHREAD_MUTEX_INITIALIZER
初始化等同于调用pthread_mutex_init
,所以调用pthread_mutex_destroy
销毁这样的互斥锁是可以的。
但是,不需要调用pthread_mutex_destroy
;资源将在程序退出时由 OS 回收。由于它是一个具有普通析构函数的对象,它不会在程序退出时作为静态清理的一部分被销毁,因此在程序结束之前都可以安全使用。
pthread 互斥锁在程序生命周期结束前一直存在似乎很常见。通常这些是使用 PTHREAD_MUTEX_INITIALIZER
.
这是一个简短但完整的代码示例,显示了我所指的内容:
#include <pthread.h>
#include <iostream>
void log(char const * const message) {
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
std::cout << message << std::endl;
pthread_mutex_unlock(&mutex);
}
struct Object final {
Object() { log("Object::Object()"); }
~Object() { log("Object::~Object()"); }
};
Object const object;
int main(int const argc, const char * argv[]) {
log("main()");
// Here the program would enter a main loop, with log() potentially being
// called from multiple threads at various times.
}
输出:
Object::Object()
main()
Object::~Object()
为简洁起见,省略了锁的错误检查和 RAII 包装。
这个例子包括一个线程安全的(至少是有意的)日志记录功能,该功能旨在在程序的整个生命周期内可用,包括在具有静态存储持续时间的对象的去初始化期间,可能(尽管在这种情况下不是) 跨多个翻译单元,这意味着取消初始化顺序可能是不确定的。
问题是没有机会安全地销毁互斥量,因为在程序生命周期的任何时候都可能需要它。 (在实践中,我不打算拥有具有静态存储持续时间和非平凡析构函数的对象,但我仍然有兴趣解决这个问题。)
出现的第一个问题是使用PTHREAD_MUTEX_INITIALIZER
初始化的互斥量是否需要使用pthread_mutex_destroy()
销毁。至少 documentation 的某些版本包含以下措辞:
In cases where default mutex attributes are appropriate, the macro PTHREAD_MUTEX_INITIALIZER can be used to initialize mutexes. The effect shall be equivalent to dynamic initialization by a call to pthread_mutex_init() with parameter attr specified as NULL, except that no error checks are performed.
这表明如果 pthread_mutex_destroy()
预期在使用 pthread_mutex_init()
初始化的互斥体上调用,那么它也应该在使用 PTHREAD_MUTEX_INITIALIZER
初始化的互斥体上调用。
但是,在网上和 Stack Overflow 上搜索时,我发现对于是否需要它存在分歧。例如,here 某人引用了一本关于 Linux 开发的书:
It is not necessary to call pthread_mutex_destroy() on a mutex that was statically initialized using PTHREAD_MUTEX_INITIALIZER.
另一方面,在 this thread 中,有人认为明确销毁这样的互斥体实际上是必需的。
我还看到它争辩说在这种情况下没有必要清理互斥体,不管它们是如何初始化的,因为资源无论如何都会被回收。 (这可能与 'construct on first use and deliberately leak memory' 有时用于单例和其他具有静态存储持续时间的对象的习惯用法背后的逻辑相同。)
我发现了一些涉及该主题的其他线程,其中混合了关于 if/how 互斥体应该被销毁的意见。我还要提到的是,我相信我已经看到来自可靠来源的生产代码,这些代码使用 PTHREAD_MUTEX_INITIALIZER
初始化互斥量并且从不销毁它们。
出于尽职调查的考虑,我在这里详细介绍了一些细节,但我的问题(我认为)相当简单。拥有从初始化到程序生命周期结束的互斥量是很有用的。我怀疑不清理此类互斥锁不会造成任何问题,但这种方法让我很烦恼。尽管有人说不需要清理使用 PTHREAD_MUTEX_INITIALIZER
初始化的互斥体,但这似乎与文档和其他人提出的各种说法相悖。
总而言之,是否有一种安全合理的方法来管理旨在在程序生命周期结束之前可用的 pthread 互斥量?这里有没有我在搜索中偶然发现的标准最佳实践?
由于用PTHREAD_MUTEX_INITIALIZER
初始化等同于调用pthread_mutex_init
,所以调用pthread_mutex_destroy
销毁这样的互斥锁是可以的。
但是,不需要调用pthread_mutex_destroy
;资源将在程序退出时由 OS 回收。由于它是一个具有普通析构函数的对象,它不会在程序退出时作为静态清理的一部分被销毁,因此在程序结束之前都可以安全使用。