Pthread_exit против возврата
У меня есть функция runted pthread runner, определенная ниже:
void *sumOfProducts(void *param)
{
...
pthread_exit(0);
}
Этот поток должен присоединиться к основному потоку.
Всякий раз, когда я запускал свою программу через Valgrind, я получал следующие утечки:
LEAK SUMMARY:
definitely lost: 0 bytes in 0 blocks
indirectly lost: 0 bytes in 0 blocks
possibly lost: 0 bytes in 0 blocks
still reachable: 968 bytes in 5 blocks
suppressed: 0 bytes in 0 blocks
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 15 from 10)
Я проверил страницу man для pthreads, в которой говорилось:
The new thread terminates in one of the following ways:
* It calls pthread_exit(3), specifying an exit status value that is
available to another thread in the same process that calls
pthread_join(3).
* It returns from start_routine(). This is equivalent to calling
pthread_exit(3) with the value supplied in the return statement.
* It is canceled (see pthread_cancel(3)).
* Any of the threads in the process calls exit(3), or the main thread
performs a return from main(). This causes the termination of all
threads in the process.
Чудесно, когда я заменил pthread_exit() оператором return, утечки исчезли.
return(NULL);
Мой фактический вопрос трижды:
- Может кто-нибудь объяснить, почему оператор возврата не дал никаких утечек?
- Есть ли какая-то принципиальная разница между обоими утверждениями в отношении выхода из потоков?
- Если да, то когда нужно быть предпочтительнее другого?
Ответы
Ответ 1
Следующий минимальный тестовый пример демонстрирует поведение, которое вы описываете:
#include <pthread.h>
#include <unistd.h>
void *app1(void *x)
{
sleep(1);
pthread_exit(0);
}
int main()
{
pthread_t t1;
pthread_create(&t1, NULL, app1, NULL);
pthread_join(t1, NULL);
return 0;
}
valgrind --leak-check=full --show-reachable=yes
отображает 5 блоков, выделенных из функций, называемых pthread_exit()
, которые являются неуправляемыми, но все же доступны при выходе процесса. Если pthread_exit(0);
заменяется на return 0;
, 5 блоков не выделяются.
Однако, если вы протестируете создание и объединение большого количества потоков, вы обнаружите, что количество свободной памяти, используемой при выходе, не увеличивается. Это и тот факт, что он все еще доступен, указывает на то, что вы просто видите странность реализации glibc. Несколько функций glibc выделяют память с помощью malloc()
при первом вызове, который они сохраняют для оставшейся части жизненного цикла процесса. glibc не беспокоится о том, чтобы освободить эту память при выходе из процесса, поскольку он знает, что процесс все равно срывается - это просто пустая трата CPU.
Ответ 2
Не уверен, что вы все еще заинтересованы в этом, но сейчас я отлаживаю подобную ситуацию. Темы, использующие pthread_exit
, заставляют valgrind сообщать о доступных блоках. Причина, по-видимому, объясняется здесь довольно хорошо:
https://bugzilla.redhat.com/show_bug.cgi?id=483821
По существу, кажется, что pthread_exit
вызывает a dlopen
, который никогда не очищается явно, когда процесс завершается.
Ответ 3
Вы действительно используете С++, случайно? Чтобы уточнить - ваш исходный файл заканчивается расширением .c
, и вы компилируете его с помощью gcc
, а не g++
?
Кажется разумным, что ваша функция выделяет ресурсы, которые вы ожидаете, что они будут автоматически очищены при возврате функции. Локальные объекты С++, такие как std::vector
или std::string
, делают это, и их деструкторы, вероятно, не будут выполняться, если вы вызываете pthread_exit
, но будете очищены, если вы только вернетесь.
Мое предпочтение - избегать низкоуровневых API, таких как pthread_exit
, и всегда просто возвращаться от функции потока, где это возможно. Они эквивалентны, за исключением того, что pthread_exit
- это де-факто конструкция управления потоком, которая обходит язык, который вы используете, но return
не делает.
Ответ 4
У меня есть опыт, что valgrind имеет трудности с отслеживанием хранилища, которое выделяется для состояния присоединяемых потоков. (Это происходит в том же направлении, что показывает caf.)
Так как кажется, что вы всегда возвращаете значение 0
, я думаю, вам, возможно, нужно присоединиться к вашим потокам с точки зрения приложения? Если так считать, что они отключаются с самого начала, это позволяет избежать выделения этой памяти.
Недостатком является то, что вы либо имеете:
- реализовать собственный барьер на
конец вашего
main
. Если вы знаете
количество потоков заблаговременно,
простой статически распределенный
pthread_barrier
.
- или выйти из
main
с помощью
pthread_exit
, чтобы вы не
убить остальные бегущие потоки
который еще не завершен.
Ответ 5
Похоже на вызов exit() (и, по-видимому, pthread_exit()) оставляет выделенные автоматически распределенные переменные. Вы должны либо вернуться, либо бросить, чтобы нормально расслабиться.
Per С++ valgrind возможные утечки в строке STL:
@Klaim: я не вижу, где этот документ говорит, что я ошибаюсь, но если это значит, что это неправильно. Чтобы процитировать стандарт С++ (§18.3/8): "Автоматические объекты не уничтожаются в результате вызова exit()". - Джеймс Макнеллис Сен 10 '10 в 19:11
Так как выполнение "return 0" вместо "pthread_exit (0)", похоже, решило вашу проблему (и мое.. спасибо), я предполагаю, что поведение похоже на два.