【问题标题】:pthread_key_create destructor not getting calledpthread_key_create 析构函数没有被调用
【发布时间】:2014-07-02 02:28:12
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

    这是设计使然。

    主线程退出(通过返回或调用exit()),并且不使用pthread_exit()。 POSIX 文档pthread_exit 调用线程特定的析构函数。

    您可以在main 的末尾添加pthread_exit()。或者,您可以使用atexit 进行销毁。在这种情况下,将特定于线程的值设置为 NULL 会很干净,因此在调用 pthread_exit 的情况下,该键的销毁不会发生两次。

    更新实际上,我只需将其添加到我的全局单元测试设置函数中就解决了我的直接担忧:

    ::atexit([] { ::pthread_exit(0); });
    

    所以,在我的全局固定类 MyConfig 的上下文中:

    struct MyConfig {
        MyConfig()   {
            GOOGLE_PROTOBUF_VERIFY_VERSION;
            ::atexit([] { ::pthread_exit(0); });
        }
        ~MyConfig()  { google::protobuf::ShutdownProtobufLibrary(); }
    };
    

    使用的一些参考资料:


    附言。当然是 c++11 introduced <thread>,所以你可以使用更好、更便携的原语。

    【讨论】:

    • 添加了一个我目前想不出有重大缺点的简洁解决方法:::atexit([] { ::pthread_exit(0); });
    • 请注意,POSIX 声明 通过调用 atexit() 注册的函数必须返回以确保调用所有注册的函数。因此,如果您在 atexit 注册函数中使用 pthread_exit(),则可能不会调用 atexit 堆栈上的任何剩余函数。
    • @AlexanderKlauer 非常好。当我再次触摸代码时,我将不得不重新考虑这个想法
    【解决方案2】:

    已经在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。

    【讨论】:

      【解决方案3】:

      我写了一个快速测试,我唯一改变的是将你的 create_key 呼叫移到 set_thread_specific 之外。

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

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

      【讨论】:

        【解决方案4】:

        我在 ma​​in() 的末尾手动调用 destructor()

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

        当然 key 应该在 ma​​in() 代码中更早地正确初始化。 PS。在 ma​​in() 的末尾调用 Pthread_Exit() 似乎会挂起整个应用程序...

        【讨论】:

          【解决方案5】:

          您最初使用 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
          }
          

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-09-23
              • 2011-11-23
              • 2015-12-23
              • 2017-11-18
              • 1970-01-01
              • 2012-09-17
              • 2019-10-05
              • 1970-01-01
              相关资源
              最近更新 更多