根据 pthread_key_create 手册页,我们可以关联一个在线程关闭时调用的析构函数。我的问题是我注册的析构函数没有被调用。我的代码要点如下。
static pthread_key_t key;
static pthread_once_t tls_init_flag = PTHREAD_ONCE_INIT;
void destructor(void *t) {
// thread local data structure clean up code here, which is not getting called
}
void create_key() {
pthread_key_create(&key, destructor);
}
// This will be called from every thread
void set_thread_specific() {
ts = new ts_stack; // Thread local data structure
pthread_once(&tls_init_flag, create_key);
pthread_setspecific(key, ts);
}
知道什么可能会阻止调用这个析构函数吗?我现在也在使用 atexit() 在主线程中进行一些清理。是否有可能干扰析构函数的调用?我也尝试删除它。但还是没用。另外,我不清楚是否应该使用 atexit 将主线程作为单独的情况处理。 (顺便说一句,必须使用 atexit,因为我需要在应用程序退出时进行一些特定于应用程序的清理)
这是设计使然。
主线程退出(通过返回或调用
exit()
),并且不使用pthread_exit()
。 POSIX 文档 pthread_exit
调用线程特定的析构函数。
您可以在
pthread_exit()
的末尾添加 main
。或者,您可以使用 atexit
来进行破坏。在这种情况下,将线程特定值设置为 NULL
会很干净,因此在调用 pthread_exit
的情况下,该键的销毁不会发生两次。
更新实际上,我只需将其添加到我的全局单元测试设置函数中就解决了我眼前的担忧:
::atexit([] { ::pthread_exit(0); });
所以,在我的全局夹具类的上下文中
MyConfig
:
struct MyConfig {
MyConfig() {
GOOGLE_PROTOBUF_VERIFY_VERSION;
::atexit([] { ::pthread_exit(0); });
}
~MyConfig() { google::protobuf::ShutdownProtobufLibrary(); }
};
使用的一些参考资料:
PS。当然,c++11 引入了
<thread>
,因此您可以使用更好、更便携的原语。
已经在sehe的回答中了,只是为了以紧凑的方式呈现要点:
pthread_key_create()
析构函数调用由对 pthread_exit()
的调用触发。pthread_exit()
(即触发析构函数调用)。main()
返回,则行为就像调用 exit()
一样 — 不会触发析构函数调用。这在 http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_create.html 中进行了解释。另请参阅 C++17 6.6.1p5 或 C11 5.1.2.2.3p1。
我写了一个快速测试,我唯一改变的就是将你的
create_key
调用移到 set_thread_specific
之外。
也就是说,我在主线程中调用它。
然后我看到当线程例程退出时我的销毁被调用。
我在 main() 末尾手动调用 destructor():
void * ThreadData = NULL;
if ((ThreadData = pthread_getspecific(key)) != NULL)
destructor(ThreadData);
当然,key应该在main()代码中正确初始化。 附言。在 main() 末尾调用 Pthread_Exit() 似乎会挂起整个应用程序...
您最初使用 atexit 将主线程作为单独的情况处理的想法最适合我。
请注意 pthread_exit(0) 会覆盖进程的退出值。例如,即使 main() 返回数字 3,以下程序也会以状态 0 退出:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
class ts_stack {
public:
ts_stack () {
printf ("init\n");
}
~ts_stack () {
printf ("done\n");
}
};
static void cleanup (void);
static pthread_key_t key;
static pthread_once_t tls_init_flag = PTHREAD_ONCE_INIT;
void destructor(void *t) {
// thread local data structure clean up code here, which is not getting called
delete (ts_stack*) t;
}
void create_key() {
pthread_key_create(&key, destructor);
atexit(cleanup);
}
// This will be called from every thread
void set_thread_specific() {
ts_stack *ts = new ts_stack (); // Thread local data structure
pthread_once(&tls_init_flag, create_key);
pthread_setspecific(key, ts);
}
static void cleanup (void) {
pthread_exit(0); // <-- Calls destructor but sets exit status to zero as a side effect!
}
int main (int argc, char *argv[]) {
set_thread_specific();
return 3; // Attempt to exit with status of 3
}
我遇到了与您类似的问题:
pthread_setspecific
设置了一个键,但析构函数永远不会被调用。为了解决这个问题,我们只需切换到 C++ 中的 thread_local
即可。如果更改太复杂,您也可以执行类似的操作:
例如,假设您有一些类
ThreadData
,您希望在线程完成执行时对其执行某些操作。您可以在以下几行中定义析构函数:
void destroy_my_data(ThreadlData* t) {
delete t;
}
当你的线程启动时,你为
ThreadData*
实例分配内存,并为其分配一个析构函数,如下所示:
ThreadData* my_data = new ThreadData;
thread_local ThreadLocalDestructor<ThreadData> tld;
tld.SetDestructorData(my_data, destroy_my_data);
pthread_setspecific(key, my_data)
注意
ThreadLocalDestructor
被定义为 thread_local。我们依赖C++11机制,当线程退出时,会自动调用ThreadLocalDestructor
的析构函数,并实现~ThreadLocalDestructor
来调用函数destroy_my_data
。
这是ThreadLocalDestructor的实现:
template <typename T>
class ThreadLocalDestructor
{
public:
ThreadLocalDestructor() : m_destr_func(nullptr), m_destr_data(nullptr)
{
}
~ThreadLocalDestructor()
{
if (m_destr_func) {
m_destr_func(m_destr_data);
}
}
void SetDestructorData(void (*destr_func)(T*), T* destr_data)
{
m_destr_data = destr_data;
m_destr_func = destr_func;
}
private:
void (*m_destr_func)(T*);
T* m_destr_data;
};
很多答案都建议调用
pthread_exit()
,但是这样做主线程不会调用exit()
或return
,因此不会调用atexit()
注册的函数。
也许这会对你有帮助:
static pthread_key_t key;
static pthread_once_t tls_init_flag = PTHREAD_ONCE_INIT;
void destructor(void *t) {
// thread local data structure clean up code here, which is not getting called
}
void cleanup(void) {
ts = pthread_getspecific(key);
if (ts != NULL) { // if "main" thread has specific data
destructor(ts); // destroy data
pthread_setspecific(key, NULL); // unset specific data
}
// if key is in a library module (so set_thread_specific is too)
// delete key at program exit.
// pthread_key_delete(key);
}
void create_key() {
pthread_key_create(&key, destructor);
atexit(cleanup); // register cleanup function for "main" thread.
}
// This will be called from every thread
void set_thread_specific() {
ts = new ts_stack; // Thread local data structure
pthread_once(&tls_init_flag, create_key);
pthread_setspecific(key, ts);
}