Подтвердить что ты не робот

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

Мой фактический вопрос трижды:

  • Может кто-нибудь объяснить, почему оператор возврата не дал никаких утечек?
  • Есть ли какая-то принципиальная разница между обоими утверждениями в отношении выхода из потоков?
  • Если да, то когда нужно быть предпочтительнее другого?
4b9b3361

Ответ 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)", похоже, решило вашу проблему (и мое.. спасибо), я предполагаю, что поведение похоже на два.