pthread_create()和内存泄漏

9
这个问题似乎被问了很多次。我有一些看起来还不错的遗留生产代码,直到它开始每天处理更多的连接。每个连接都会启动一个新线程。最终,它将耗尽内存并崩溃。
我正在回顾我多年没有接触过的pthread(和C套接字)。我学习的教程很有用,但是当我使用top时也看到了同样的情况。所有线程都退出了,但仍然占用了一些虚拟内存。Valgrind告诉我在调用pthread_create()时可能会有内存泄漏。以下是非常基本的示例代码。
最可怕的部分是,当所有线程退出时,pthread_exit(NULL)似乎会留下约100m的VIRT未计算。如果我注释掉这行,它就更容易处理,但仍然存在一些问题。在我的系统上,它从大约14k开始,以47k结束。
如果我将线程数增加到10,000,则VIRT会增加到70多GB,但是假设我注释掉pthread_exit(NULL),则会在50k左右结束。如果我使用pthread_exit(NULL),则最终仍然有约113m的VIRT。这些是否可以接受?top是否没有告诉我全部信息?
void* run_thread( void* id )
{
    int thread_id = *(int*)id;
    int count = 0;
    while ( count < 10 ) {
        sleep( 1 );
        printf( "Thread %d at count %d\n", thread_id, count++ );
    }

    pthread_exit( NULL );
    return 0;
}

int main( int argc, char* argv[] )
{
    sleep( 5 ); 
    int thread_count    = 0;
    while( thread_count < 10 ) {
        pthread_t my_thread;
        if ( pthread_create( &my_thread, NULL, run_thread, (void*)&thread_count ) < 0 )   {
            perror( "Error making thread...\n" );
            return 1;
        }

        pthread_detach( my_thread );
        thread_count++;
        sleep( 1 );
    }

    pthread_exit( 0 );  // added as per request
    return 0;
}

我假设您正在报告数字的valgrind结果。您能否在main()中的return 0之前添加pthread_exit(0) - jxh
添加这个似乎有帮助...至少在valgrind中是这样的。'top'仍然显示有一些虚拟内存仍在使用。程序在启动线程之前有14k的VIRT,在启动线程后现在有110m。不过这似乎是静态的。无论线程数是10还是1000,VIRT中的100m都在那里。我想我应该相信valgrind? - kiss-o-matic
3
是的,Valgrind报告的内存问题的原因是并非所有线程都在主线程结束之前完成。添加thread_exit()函数调用可以解决这个问题。 - jxh
1
与内存泄漏无关,但您正在将相同的本地“thread_count”变量地址传递给每个新线程。根据线程启动的速度,它可能会看到正确的值,也可能会看到更新的更高值。您需要在堆上分配一个新变量以传递给每个子线程,或者使用整数转换为“(void *)”。 - Adam Rosenfield
2个回答

8

我知道这个问题已经比较旧了,但是我希望其他人也能从中受益。

这确实是一个内存泄漏问题。线程是使用默认属性创建的。默认情况下,线程是可联接的。可联接线程会保留其底层的记账信息直到它完成并被联接。

如果一个线程从未被联接,则设置Detached属性。一旦线程终止,所有(线程)资源都将被释放。

以下是一个示例:

pthread_attr_t attr;
pthread_t thread;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, 1);
pthread_create(&thread, &attr, &threadfunction, NULL);
pthread_attr_destroy(&attr);

2
非常好 - 谢谢您为后人回答一个老问题。当然,我现在只使用std::thread,它似乎更好地封装了所有这些。 :) - kiss-o-matic

7
在你将pthread_exit(0)添加到main()的结尾之前,你的程序在所有线程完成运行之前就会结束执行。因此,valgrind报告了那些仍然被保持的资源,这些资源是在程序终止时仍然活动的线程所拥有的,使得你的程序看起来像是存在内存泄漏。
main()中调用pthread_exit(0)使主线程等待所有其他生成的线程退出后再退出自己。这使得valgrind可以观察内存使用方面的清洁运行。
(我假设你的操作系统是Linux,但从你的评论中看,似乎你正在运行某种UNIX系统。)
你看到的额外虚拟内存只是Linux为你的程序分配了一些页面,因为它是一个大内存使用者。只要你的常驻内存利用率在空闲状态下保持低且恒定,虚拟利用率相对恒定,就可以认为你的系统表现良好。
在Linux上,默认情况下,每个线程都有2MB的堆栈空间。如果每个线程堆栈不需要那么多空间,你可以通过初始化pthread_attr_t并使用pthread_attr_setstacksize()设置较小的堆栈大小来调整它。适当的堆栈大小取决于函数调用堆栈的深度以及这些函数的本地变量占用的空间量。
#define SMALLEST_STACKSZ PTHREAD_STACK_MIN
#define SMALL_STACK (24*1024)
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, SMALL_STACK);
/* ... */
pthread_create(&my_thread, &attr, run_thread, (void *)thread_count);
/* ... */
pthread_attr_destroy(&attr);

非常感谢 - 那很有道理! - kiss-o-matic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接