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

Отдельные pthreads и утечка памяти

Может кто-нибудь, пожалуйста, объясните мне, почему этот простой код утечки памяти?

Я считаю, что, поскольку pthreads создаются с отключенным состоянием, их ресурсы должны быть выпущены inmediatly после его завершения, но это не так.

Моя среда Qt5.2.

#include <QCoreApplication>
#include <windows.h>

void *threadFunc( void *arg )
    {
    printf("#");
    pthread_exit(NULL);
    }

int main()
    {
    pthread_t thread;
    pthread_attr_t attr;

    while(1)
        {
        printf("\nStarting threads...\n");
        for(int idx=0;idx<100;idx++)
            {
            pthread_attr_init(&attr);
            pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
            pthread_create( &thread, &attr, &threadFunc, NULL);
            pthread_attr_destroy ( &attr );
            }
        printf("\nSleeping 10 seconds...\n");
        Sleep(10000);
        }
    }

Memory leak graph

UPDATE:

Я обнаружил, что если я добавлю небольшую задержку в 5 миллисекунд внутри цикла for, утечка будет WAY медленнее:

    for(int idx=0;idx<100;idx++)
        {
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        pthread_create( &thread, &attr, &threadFunc, NULL);
        pthread_attr_destroy ( &attr );
        Sleep(5); /// <--- 5 MILLISECONDS DELAY ///
        }

Memory leak graph with slight delay Это волнует меня, может кто-нибудь, пожалуйста, скажите мне, что происходит? Как эта небольшая задержка может привести к столь значительным изменениям? (или каким-либо образом изменить поведение)

Приветствуются любые советы.

Спасибо.

UPDATE2:

Эта утечка наблюдалась на платформах Windows (W7 и XP), на платформах Linux не наблюдалось утечки (спасибо @MichaelGoren)

4b9b3361

Ответ 1

Я проверил программу с небольшими изменениями в окнах, используя cygwin, и потребление памяти было устойчивым. Так что это должна быть проблема qt; библиотека pthread на cygwin отлично работает без утечки.

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>


void *threadFunc( void *arg )
{
printf("#");
pthread_exit(NULL);
}

int main()
{
pthread_t thread;
pthread_attr_t attr;
int idx;

while(1)
    {
    printf("\nStarting threads...\n");
    for(idx=0;idx<100;idx++)
        {
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        pthread_create( &thread, &attr, &threadFunc, NULL);
        pthread_attr_destroy ( &attr );
        }
    printf("\nSleeping 10 seconds...\n");
    //Sleep(10000);
sleep(10);
    }
}

Ответ 2

Оптимизация компилятора или ОС, которые он сам может решить сделать разворот цикла. Это ваш цикл for имеет постоянную границу (100 здесь). Поскольку для предотвращения этого не существует явной синхронизации, вновь созданный отсоединенный поток может умереть и иметь идентификатор потока, переназначенный в другой новый поток, прежде чем его создатель вернется из pthread_create() из-за этого разворота. Следующая итерация уже запущена до того, как поток был фактически уничтожен.

Это также объясняет, почему ваша добавленная небольшая задержка имеет меньше проблем; одна итерация занимает больше времени, и, следовательно, функции потоков фактически могут быть закончены в большинстве случаев, и, следовательно, потоки фактически завершаются большую часть времени.

Возможное исправление - отключить оптимизацию компилятора или добавить синхронизацию; то есть вы проверяете, существует ли поток в конце кода, если вам это нужно, вам придется ждать завершения функции. Более сложным способом было бы использовать мьютексы; вы разрешаете нить требовать ресурс при создании и по определению PTHREAD_CREATE_DETACHED этот ресурс автоматически освобождается при выходе потока, поэтому вы можете использовать try_lock для проверки того, действительно ли поток завершен. Обратите внимание, что я не тестировал этот подход, поэтому я не уверен, действительно ли PTHREAD_CREATE_DETACHED работает в соответствии с его определением...

Концепция:

pthread_mutex_t mutex;

void *threadFunc( void *arg )
{
  printf("#");
  pthread_mutex_lock(&mutex);
  pthread_exit(NULL);
}

for(int idx=0;idx<100;idx++)
{
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  pthread_create( &thread, &attr, &threadFunc, NULL);
  pthread_attr_destroy ( &attr );
  pthread_mutex_lock(&mutex); //will block untill "destroy" has released the mutex
  pthread_mutex_unlock(&mutex);
}

Ответ 3

Это не имеет ничего общего с оптимизацией компилятора. Код в порядке. Проблемой может быть  a) Windows сама.  б) реализация Qt pthread_create() с отсоединенными атрибутами

Проверка для (a): попытайтесь создать много быстрых отсоединенных потоков непосредственно с помощью Windows _beginthreadex и посмотреть, получится ли у вас одинаковое изображение. Примечание: CloseHandle (thread_handle), как только _beginthreaex вернется, чтобы сделать его отсоединенным.

Проверка для (b): отслеживать, какую функцию Qt использует для создания потоков. Если это _beginthread, тогда есть ваш ответ. Если это _beginthreadex, то Qt делает правильную вещь, и вам нужно проверить, закрывает ли Qt дескриптор дескриптора потока. Если это не так, это и есть причина.

веселит

ОБНОВЛЕНИЕ 2

Qt5.2.0 не предоставляет API pthreads и вряд ли отвечает за обнаруженные утечки.

Я обернул родные окна api, чтобы увидеть, как код работает без библиотеки pthread. Вы можете включить этот фрагмент сразу после включения:

#include <process.h>
#define PTHREAD_CREATE_JOINABLE 0
#define PTHREAD_CREATE_DETACHED 1

typedef struct { int detachstate; } pthread_attr_t;

typedef HANDLE pthread_t;

_declspec(noreturn) void pthread_exit(void *retval)
{
    static_assert(sizeof(unsigned) == sizeof(void*), "Modify code");
    _endthreadex((unsigned)retval);
}

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
{
    attr->detachstate = detachstate;
    return 0;
}

int pthread_attr_init(pthread_attr_t *attr)
{
    attr->detachstate = PTHREAD_CREATE_JOINABLE;
    return 0;
}

int pthread_attr_destroy(pthread_attr_t *attr)
{
    (void)attr;
    return 0;
}

typedef struct {
    void *(*start_routine)(void *arg);
    void   *arg;
} winapi_caller_args;

unsigned __stdcall winapi_caller(void *arglist)
{
    winapi_caller_args *list = (winapi_caller_args *)arglist;
    void             *(*start_routine)(void *arg) = list->start_routine;
    void               *arg                       = list->arg;

    free(list);
    static_assert(sizeof(unsigned) == sizeof(void*), "Modify code");
    return (unsigned)start_routine(arg);
}

int pthread_create( pthread_t *thread, pthread_attr_t *attr,
                    void *(*start_routine)(void *), void *arg)
{

    winapi_caller_args *list;

    list = (winapi_caller_args *)malloc(sizeof *list);
    if (list == NULL)
        return EAGAIN;

    list->start_routine = start_routine;
    list->arg = arg;
    *thread = (HANDLE)_beginthreadex(NULL, 0, winapi_caller, list, 0, NULL);
    if (*thread == 0) {
        free(list);
        return errno;
    }
    if (attr->detachstate == PTHREAD_CREATE_DETACHED)
        CloseHandle(*thread);

    return 0;
}

С прокомментированной строкой Sleep() она работает нормально без утечек. Время работы = 1 ч.

Если код с выключенной строкой Sleep вызывается из библиотеки Pthreads-win32 2.9.1 (предварительно созданный для MSVC), то программа прекращает создавать новые потоки и перестает отвечать после 5..10 минут.

Условия тестирования: XP Home, MSVC 2010 Expresss, Qt5.2.0 qmake и т.д.

Ответ 4

Задержка может вызвать большое изменение в поведении, поскольку оно дает выходное время потока! Конечно, как ваша библиотека pthread реализована, также является фактором здесь. Я подозреваю, что он использует оптимизацию "свободного списка".

Если вы создаете 1000 потоков одновременно, тогда библиотека выделяет для них память, прежде чем любое значительное число этих потоков может выйти.

Если, как и в вашем втором примере кода, вы разрешаете предыдущему потоку и, вероятно, выходите перед тем, как начать новый поток, тогда ваша библиотека потоков может повторно использовать выделенную потоком память или структуры данных, которые она теперь знает, больше не нужны, и это теперь, вероятно, держится в свободном списке на всякий случай, когда кто-то снова создает поток, и он может эффективно перерабатывать память.

Ответ 5

Вы забыли присоединиться к своей теме (даже если они уже закончены). Правильный код должен быть:

        pthread_t arr[100];
        for(int idx=0;idx<100;idx++)
        {
            pthread_attr_init(&attr);
            pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
            pthread_create( &arr[idx], &attr, &threadFunc, NULL);
            pthread_attr_destroy ( &attr );
        }

        Sleep(2000);

        for(int idx=0;idx<100;idx++)
        {
            pthread_join(arr[idx]);
        }    

Примечание на странице руководства:

   Failure to join with a thread that is joinable (i.e., one that is not detached), produces a "zombie thread".  Avoid doing this, since each zombie thread consumes some system resources, and  when  enough  zombie  threads  have
   accumulated, it will no longer be possible to create new threads (or processes).