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(); }
};
Некоторые из использованных ссылок:
- 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. Конечно, 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;
};