pthread_key_create析构函数没有被调用

时间:2014-07-02 02:28:13

标签: c++ pthreads shutdown thread-specific-storage

根据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是必须的,因为我需要在应用程序出口处进行一些特定于应用程序的清理)

6 个答案:

答案 0 :(得分:3)

这是设计的。

主线程退出(通过返回或调用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 introduced <thread>所以你可以使用更好更便携的原语。

答案 1 :(得分:1)

它已经在回答中,只是为了以紧凑的方式呈现关键点:

  • 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。

答案 2 :(得分:0)

我写了一个快速测试,我唯一改变的就是在create_key之外移动你的set_thread_specific电话。

也就是说,我在主线程中调用了它。

然后,当线程例程退出时,我看到我的destroy被调用。

答案 3 :(得分:0)

我在 main()末尾手动调用析构函数()

void * ThreadData = NULL;

if ((ThreadData = pthread_getspecific(key)) != NULL)
        destructor(ThreadData);

当然密钥应该在 main()代码中提前正确初始化。 PS。在 main()的末尾调用 Pthread_Exit()似乎会挂起整个应用程序......

答案 4 :(得分:0)

您最初想到将主线程作为一个单独的案例与atexit一起使用对我来说是最好的。

要知道pthread_exit(0)会覆盖进程的退出值。例如,即使main()返回数字3,以下程序也将以零状态退出:

#word_list = ["one", "two", "four"]
#word_list = ["three", "five", "six", "seven", "nine"]
word_list = ["one", "two", "three", "five", "four"]
#word_list = ["one", "two", "three", "five"]
def has_o_in_it(words):
  for word in words:
    if "o" not in word:
      return False
  return True

if has_o_in_it(word_list):
  print("word list has o in it")
else:
  print("word list has no o in it")

答案 5 :(得分:0)

我遇到了与您类似的问题: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;
};