分离的pthreads和内存泄漏

时间:2014-01-17 15:17:13

标签: c++ windows memory-leaks pthreads

有人可以向我解释为什么这个简单的代码会泄漏内存吗?

我相信由于pthreads是以脱离状态创建的,因此它们的资源应该在终止后立即释放,但事实并非如此。

我的环境是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

更新

我发现如果我在for循环中添加5毫秒的轻微延迟,则泄漏 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)

5 个答案:

答案 0 :(得分:1)

我使用cygwin在windows上略微修改了程序,内存消耗稳定。所以它必须是一个qt问题; cygwin上的pthread库工作正常,没有泄漏。

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

答案 1 :(得分:0)

编译器优化或自己可以决定循环展开的操作系统。那就是你的for循环有一个常数界限(这里是100)。由于没有明确的同步来阻止它,因此新创建的分离线程可能会死亡,并且在其创建者从pthread_create()返回之前将其线程ID重新分配给另一个新线程。在线程实际销毁之前,下一次迭代已经开始。

这也解释了为什么你增加的轻微延迟问题较少;一次迭代需要更长的时间,因此线程函数实际上可以在更多情况下完成,因此线程实际上在大多数时间终止。

可能的解决方法是禁用编译器优化或添加同步;也就是说,你检查线程是否仍然存在,在代码的末尾,如果是,你将不得不等待函数完成。 一种更棘手的方法是使用互斥锁;您让线程在创建时声明资源,并根据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);
}

答案 2 :(得分:0)

它与编译器优化无关。代码很好。问题可能是  a)Windows本身。  b)使用分离属性的pthread_create()的Qt实现

检查(a):尝试直接使用Windows _beginthreadex创建许多快速分离的线程,看看你是否得到了相同的图片。注意:一旦_beginthreaex返回以使其分离,CloseHandle(thread_handle)。

检查(b):跟踪Qt用于创建线程的函数。如果它是_beginthread那么就有你的答案。如果它是_beginthreadex,那么Qt正在做正确的事情,你需要检查Qt是否立即关闭线程句柄。如果没有那就是原因。

欢呼声

更新2

Qt5.2.0不提供pthreads API,并且不太可能对观察到的泄漏负责。

我包装了原生的windows 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 line注释掉的代码是调用Pthreads-win32 2.9.1库(预建MSVC),则程序会停止生成新线程并在5..10分钟后停止响应。

测试环境:XP Home,MSVC 2010 Expresss,Qt5.2.0 qmake等。

答案 3 :(得分:0)

延迟会导致行为发生很大变化,因为它会让线程有时间退出!当然,如何实现pthread库也是一个因素。我怀疑它正在使用“免费列表”优化。

如果你一次创建1000个线程,那么在任何大量的线程退出之前,库都会为它们分配内存。

如果在你的第二个代码示例中你让前一个线程运行并且可能在你开始一个新线程之前退出,那么你的线程库可以重用该线程的已分配的内存或数据结构,它现在已经知道它们不再需要了,它是现在可能会保留一个空闲列表,以防有人再次创建一个线程,它可以有效地回收内存。

答案 4 :(得分:-1)

你忘了加入你的主题(即使它们已经完成)。 正确的代码应该是:

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