C++ 分离的pthreads和内存泄漏

C++ 分离的pthreads和内存泄漏,c++,windows,memory-leaks,pthreads,C++,Windows,Memory Leaks,Pthreads,有人能给我解释一下为什么这个简单的代码会泄露内存吗 我认为,由于pthread是使用分离状态创建的,所以它们的资源应该在终止后在中间释放,但事实并非如此 我的环境是Qt5.2 #include <QCoreApplication> #include <windows.h> void *threadFunc( void *arg ) { printf("#"); pthread_exit(NULL); } int main() {

有人能给我解释一下为什么这个简单的代码会泄露内存吗

我认为,由于pthread是使用分离状态创建的,所以它们的资源应该在终止后在中间释放,但事实并非如此

我的环境是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);
        }
    }
这太吓人了,有人能告诉我发生了什么事吗?这一微小的延迟是如何产生如此重大的变化的?(或以任何方式改变行为)

如有任何建议,将不胜感激

谢谢

更新2:


在Windows平台(W7和XP)上观察到此漏洞,在Linux平台(谢谢@MichaelGoren)

编译器优化或它自己可以决定执行循环展开的操作系统上未观察到此漏洞。这就是for循环有一个常数界(此处为100)。由于没有显式同步来阻止它,新创建的分离线程可能会死亡,并在其创建者由于此展开而从pthread_create()返回之前将其线程ID重新分配给另一个新线程。下一次迭代在线程实际销毁之前就已经开始了

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

一个可能的修复方法是禁用编译器优化,或添加同步;也就是说,在代码末尾检查线程是否仍然存在,如果仍然存在,则必须等待函数完成。 更棘手的方法是使用互斥锁;您让线程在创建时声明一个资源,根据
PTHREAD\u CREATE\u DETACHED
的定义,该资源在线程退出时自动释放,因此您可以使用
try\u lock
测试线程是否实际完成。请注意,我还没有测试过这种方法,所以实际上我不确定
PTHREAD\u CREATE\u 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);
}
pthread\u mutex\u t mutex;
void*threadFunc(void*arg)
{
printf(“#”);
pthread_mutex_lock(&mutex);
pthread_exit(NULL);
}

对于(intidx=0;idx您忘记加入线程(即使它们已经完成)。 正确的代码应为:

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

这和编译器优化无关。代码很好。问题可能是 a) 窗户本身。 b) 具有分离属性的pthread_create()的Qt实现

检查(a):尝试直接使用Windows _beginthreadex创建许多快速分离的线程,看看是否得到相同的结果。注意:CloseHandle(线程句柄)一旦开始,ThreAEX就会返回以使其分离

检查(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;
}
#包括
#定义PTHREAD_创建可连接0
#定义PTHREAD_创建_1
typedef结构{int-detachstate;}pthread\u attr\t;
typedef HANDLE pthread_t;
_declspec(noreturn)void pthread_exit(void*retval)
{
静态断言(sizeof(unsigned)=sizeof(void*),“修改代码”);
_endthreadex((未签名)retval);
}
int pthread_attr_setdetachstate(pthread_attr_t*attr,int detachstate)
{
属性->分离状态=分离状态;
返回0;
}
int pthread_attr_init(pthread_attr_t*attr)
{
attr->detachstate=PTHREAD\u CREATE\u JOINABLE;
返回0;
}
int pthread\u attr\u destroy(pthread\u attr\u t*attr)
{
(无效)属性;
返回0;
}
类型定义结构{
void*(*开始例行程序)(void*arg);
void*arg;
}winapi_调用者_参数;
未签名的stdcall winapi调用方(void*arglist)
{
winapi_调用者_参数*列表=(winapi_调用者_参数*)参数列表;
void*(*开始例行程序)(void*arg)=列表->开始例行程序;
void*arg=list->arg;
免费(名单);
静态断言(sizeof(unsigned)=sizeof(void*),“修改代码”);
返回(未签名)启动_例程(arg);
}
int pthread_create(pthread_t*thread,pthread_attr_t*attr,
void*(*开始_例程)(void*),void*arg)
{
winapi_调用者_参数*列表;
列表=(winapi_调用者_参数*)malloc(sizeof*列表);
if(list==NULL)
返回伊根;
列表->启动\例行程序=启动\例行程序;
列表->arg=arg;
*thread=(HANDLE)_beginthreadex(NULL,0,winapi_调用者,list,0,NULL);
如果(*线程==0){
免费(名单);
返回errno;
}
if(attr->detachstate==PTHREAD\u CREATE\u DETACHED)
闭合手柄(*螺纹);
返回0;
}
有了Sleep()行注释,它工作正常,没有泄漏。运行时间=约1小时

如果注释掉Sleep行的代码正在调用Pthreads-win32 2.9.1库(为MSVC预构建),那么程序将停止生成新线程,并在5..10分钟后停止响应


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

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

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

如果在第二个代码示例中,您让前一个线程运行,并且可能在启动新线程之前退出,那么您的线程库可以重用该线程分配的内存或数据结构,它现在知道不再需要这些内存或数据结构,并且它现在可能保存在f中
   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).
#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;
}
#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);
    }
}