pthread_key_create деструктор не вызывается

Согласно справочной странице 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 необходимо использовать, так как мне нужно выполнить некоторую очистку приложения при выходе из приложения)

5 ответов

Это по замыслу.

Основной поток завершается (возвращением или вызовом 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> так что у вас есть лучшие и более портативные примитивы для работы.

Это уже в ответе Сехе, просто представить ключевые моменты в сжатой форме:

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

Я вызываю destructor() вручную в конце main():

void * ThreadData = NULL;

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

Конечно, ключ должен быть правильно инициализирован ранее в коде main(). PS. Вызов Pthread_Exit() в конце main(), кажется, вешает все приложение...

Ваша первоначальная мысль об обработке основного потока как отдельного случая с atexit работала лучше всего для меня.

Имейте в виду, что pthread_exit(0) перезаписывает значение выхода процесса. Например, следующая программа выйдет с нулевым статусом, хотя main() вернется с номером три:

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

Я написал быстрый тест, и единственное, что я изменил, было перемещение create_key Ваш звонок за пределами set_thread_specific,

То есть я назвал это в основном потоке.

Затем я увидел, как вызывается мой destroy при выходе из подпрограммы потока.

У меня была такая же проблема, как у вас: pthread_setspecific устанавливает ключ, но деструктор никогда не вызывается. Чтобы это исправить, мы просто переключились на thread_local в C++. Вы также можете сделать что-то вроде этого, если это изменение слишком сложно:

Например, предположим, у вас есть какой-то класс 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;
};
Другие вопросы по тегам