pthread: уничтожение глобального статического мьютекса

Этот код был взят из 3-го издания Advanced Programming in the UNIX Environment, написанного Ричардом Стивенсом. Это пример того, как сделать реентерабельную версию getenv(). Это демонстрируется здесь только в учебных целях.

/* Copyright (c) W.R.Stevens */
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>

extern char **environ;

pthread_mutex_t env_mutex;

static pthread_once_t init_done = PTHREAD_ONCE_INIT;

static void
thread_init(void)
{
    pthread_mutexattr_t attr;

    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&env_mutex, &attr);
    pthread_mutexattr_destroy(&attr);
}

int
getenv_r(const char *name, char *buf, int buflen)
{
    int i, len, olen;

    pthread_once(&init_done, thread_init);
    len = strlen(name);
    pthread_mutex_lock(&env_mutex);
    for (i = 0; environ[i] != NULL; i++) {
        if ((strncmp(name, environ[i], len) == 0) &&
          (environ[i][len] == '=')) {
            olen = strlen(&environ[i][len+1]);
            if (olen >= buflen) {
                pthread_mutex_unlock(&env_mutex);
                return(ENOSPC);
            }
            strcpy(buf, &environ[i][len+1]);
            pthread_mutex_unlock(&env_mutex);
            return(0);
        }
    }
    pthread_mutex_unlock(&env_mutex);
    return(ENOENT);
}

Этот код легко понять. У меня есть вопрос. Мы никогда не вызываем pthread_mutex_destroy(), а это значит, что при выходе может произойти утечка памяти (думаю, на разных платформах это может быть по-разному).

Первое, что приходит на ум, это то, что можно использовать PTHREAD_MUTEX_INITIALIZER. Требуется ли pthread_mutex_init() вызов? Если нет, то нет необходимости вызывать pthread_mutex_destroy(). Однако мьютекс будет нерекурсивным.

Можно было бы написать простой класс C++, который мог бы уничтожить мьютекс в деструкторе. Однако он не подходит для тех, у кого есть только компилятор C (и использование компилятора C++ из-за одной функции кажется бредом).

Еще одна вещь, которая приходит на ум, это специфичные для компилятора расширения, такие как __attribute__((destructor)) в GCC (и, надеюсь, clang). Однако он не является портативным.

Можно ли избежать утечки памяти? Если да, то как это сделать на C?

ОБНОВЛЕНИЕ Как видно из статьи Дэвида Бутенхофа «Программирование с использованием потоков POSIX», нам никогда не нужно уничтожать вариант PTHREAD_MUTEX_INITIALIZER. Как насчет мьютексов с другими атрибутами?


person ghostmansd    schedule 13.11.2014    source источник


Ответы (2)


Ресурсы, которые все еще активны на момент завершения процесса, не являются утечками памяти, несмотря на то, что некоторые наивные инструменты классифицируют их как таковые. Утечка памяти — это необратимый и неограниченный рост требований программы к ресурсам в течение ее жизни, который непропорционален фактическому рабочему набору.

В соответствии с POSIX (где вы получаете потоки POSIX) все локальные ресурсы процесса перестают существовать при завершении программы. Нет необходимости уничтожать/освобождать их явно, а в некоторых случаях, таких как ваш, вы не можете безопасно уничтожить/освободить их и вам не следует пытаться.

person R.. GitHub STOP HELPING ICE    schedule 13.11.2014
comment
+1 отличный ответ. Вносит изменения в карго-культ «вы всегда должны явно уничтожать все ресурсы перед завершением вашего процесса» (даже если это сложно/опасно и требует загрузки дополнительного кода и тестирования). - person Martin James; 13.11.2014

Нет утечки памяти, потому что pthread_mutex_t переменные находятся в пользовательской памяти, при выходе из процесса вся выделенная пользователем память освобождается. Утечки памяти происходят, когда что-то выделяет память в куче, например, strdup. А то не уберешь.

Если бы он был выделен как

pthread_mutex_t *foo;
....
foo =malloc(sizeof(pthread_mutex_t);

И затем не освобождается со свободным, что создает утечку памяти - если затем foo будет выделено больше новой памяти. Однако при завершении процесса ВСЯ память, запрошенная процессом, включая кучу, освобождается ОС. Стивенс объясняет это в главе о процессах. Этот код не протекает.

person jim mcnamara    schedule 13.11.2014
comment
Я думал об ассигнованиях во время pthread_mutex_init(). Если существуют и init(), и destroy(), должны ли мы заботиться об их обоих? - person ghostmansd; 13.11.2014
comment
Вам действительно не нужно вызывать их обоих, если ваш мьютекс длится столько же, сколько и вся ваша программа. Но представьте, что вы создаете, скажем, связанный список, в котором есть мьютекс, и вы используете этот список только для одной функции, а затем free()d его. Тогда вам нужно убедиться, что вы вызвали pthread_mutex_destroy(), в противном случае, если вы будете вызывать эту функцию много раз в ходе выполнения вашей программы, эти утечки будут накапливаться. - person Crowman; 13.11.2014