pthread_key_create destructor not getting called

2020-08-23 06:26发布

问题:

As per pthread_key_create man page we can associate a destructor to be called at thread shut down. My problem is that the destructor function I have registered is not being called. Gist of my code is as follows.

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);
}

Any idea what might prevent this destructor being called? I am also using atexit() at moment to do some cleanup in the main thread. Is there any chance that is interfering with destructor function being called? I tried removing that as well. Still didn't work though. Also I am not clear if I should handle the main thread as a separate case with atexit. (It's a must to use atexit by the way, since I need to do some application specific cleanup at application exit)

回答1:

This is by design.

The main thread exits (by returning or calling exit()), and that doesn't use pthread_exit(). POSIX documents pthread_exit calling the thread-specific destructors.

You could add pthread_exit() at the end of main. Alternatively, you can use atexit to do your destruction. In that case, it would be clean to set the thread-specific value to NULL so in case the pthread_exit was invoked, the destruction wouldn't happen twice for that key.

UPDATE Actually, I've solved my immediate worries by simply adding this to my global unit test setup function:

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

So, in context of my global fixture class MyConfig:

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

Some of the references used:

  • http://www.resolvinghere.com/sof/6357154.shtml
  • https://sourceware.org/ml/pthreads-win32/2008/msg00007.html
  • http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_key_create.html
  • http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_exit.html

PS. Of course c++11 introduced <thread> so you have better and more portable primitves to work with.



回答2:

It's already in sehe's answer, just to present the key points in a compact way:

  • pthread_key_create() destructor calls are triggered by a call to pthread_exit().
  • If the start routine of a thread returns, the behaviour is as if pthread_exit() was called (i. e., destructor calls are triggered).
  • However, if main() returns, the behaviour is as if exit() was called — no destructor calls are triggered.

This is explained in http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_create.html. See also C++17 6.6.1p5 or C11 5.1.2.2.3p1.



回答3:

I wrote a quick test and the only thing I changed was moving the create_key call of yours outside of the set_thread_specific.

That is, I called it within the main thread.

I then saw my destroy get called when the thread routine exited.



回答4:

I call destructor() manually at the end of main():

void * ThreadData = NULL;

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

Of course key should be properly initialized earlier in main() code. PS. Calling Pthread_Exit() at the end to main() seems to hang entire application...



回答5:

Your initial thought of handling the main thread as a separate case with atexit worked best for me.

Be ware that pthread_exit(0) overwrites the exit value of the process. For example, the following program will exit with status of zero even though main() returns with number three:

#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:

I had similar issue as yours: pthread_setspecific sets a key, but the destructor never gets called. To fix it we simply switched to thread_local in C++. You could also do something like if that change is too complicated:

For example, assume you have some class ThreadData that you want some action to be done on when the thread finishes execution. You define the destructor something on these lines:

void destroy_my_data(ThreadlData* t) {
   delete t;
}

When your thread starts, you allocate memory for ThreadData* instance and assign a destructor to it like this:

ThreadData* my_data = new ThreadData;
thread_local ThreadLocalDestructor<ThreadData> tld;
tld.SetDestructorData(my_data, destroy_my_data);
pthread_setspecific(key, my_data)

Notice that ThreadLocalDestructor is defined as thread_local. We rely on C++11 mechanism that when the thread exits, the destructor of ThreadLocalDestructor will be automatically called, and ~ThreadLocalDestructor is implemented to call function destroy_my_data.

Here is the implementation of 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;
};