C++11 当使用本地线程时,gcc 4.8.1中的内存泄漏?

C++11 当使用本地线程时,gcc 4.8.1中的内存泄漏?,c++11,thread-local-storage,gcc4.8,C++11,Thread Local Storage,Gcc4.8,Valgrind在以下代码中报告泄漏的块,显然每个线程一个: #include <iostream> #include <thread> #include <mutex> #include <list> #include <chrono> std::mutex cout_mutex; struct Foo { Foo() { std::lock_guard<std::mutex> lo

Valgrind在以下代码中报告泄漏的块,显然每个线程一个:

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <chrono>

std::mutex cout_mutex;

struct Foo
{
    Foo() 
    { 
        std::lock_guard<std::mutex> lock( cout_mutex );
        std::cout << __PRETTY_FUNCTION__ << '\n'; 
    }

    ~Foo() 
    { 
        std::lock_guard<std::mutex> lock( cout_mutex );
        std::cout << __PRETTY_FUNCTION__ << '\n'; 
    }

    void 
    hello_world() 
    { 
        std::lock_guard<std::mutex> lock( cout_mutex );
        std::cout << __PRETTY_FUNCTION__ << '\n'; 
    }
};

void
hello_world_thread()
{
    thread_local Foo foo;

    // must access, or the thread local variable may not be instantiated
    foo.hello_world();

    // keep the thread around momentarily
    std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
}

int main()
{
    for ( int i = 0; i < 100; ++i )
    {
        std::list<std::thread> threads;

        for ( int j = 0; j < 10; ++j )
        {
            std::thread thread( hello_world_thread );
            threads.push_back( std::move( thread ) );
        }

        while ( ! threads.empty() )
        {
            threads.front().join();
            threads.pop_front();
        }
    }
}
GCC构建选项:

--enable-shared
--enable-threads=posix
--enable-__cxa_atexit
--enable-clocale=gnu
--enable-cxx-flags='-fno-omit-frame-pointer -g3'
--enable-languages=c,c++
--enable-libstdcxx-time=rt
--enable-checking=release
--enable-build-with-cxx
--disable-werror
--disable-multilib
--disable-bootstrap
--with-system-zlib
valgrind --leak-check=full --verbose ./a.out > /dev/null
程序编译选项:

g++ -std=gnu++11 -Og -g3 -Wall -Wextra -fno-omit-frame-pointer thread_local.cc
valgrind版本:

$ valgrind --version
valgrind-3.8.1
Valgrind选项:

--enable-shared
--enable-threads=posix
--enable-__cxa_atexit
--enable-clocale=gnu
--enable-cxx-flags='-fno-omit-frame-pointer -g3'
--enable-languages=c,c++
--enable-libstdcxx-time=rt
--enable-checking=release
--enable-build-with-cxx
--disable-werror
--disable-multilib
--disable-bootstrap
--with-system-zlib
valgrind --leak-check=full --verbose ./a.out > /dev/null
valgrind输出的尾端:

==1786== HEAP SUMMARY:
==1786==     in use at exit: 24,000 bytes in 1,000 blocks
==1786==   total heap usage: 3,604 allocs, 2,604 frees, 287,616 bytes allocated
==1786== 
==1786== Searching for pointers to 1,000 not-freed blocks
==1786== Checked 215,720 bytes
==1786== 
==1786== 24,000 bytes in 1,000 blocks are definitely lost in loss record 1 of 1
==1786==    at 0x4C29969: operator new(unsigned long, std::nothrow_t const&) (vg_replace_malloc.c:329)
==1786==    by 0x4E8E53E: __cxa_thread_atexit (atexit_thread.cc:119)
==1786==    by 0x401036: hello_world_thread() (thread_local.cc:34)
==1786==    by 0x401416: std::thread::_Impl<std::_Bind_simple<void (*())()> >::_M_run() (functional:1732)
==1786==    by 0x4EE4830: execute_native_thread_routine (thread.cc:84)
==1786==    by 0x5A10E99: start_thread (pthread_create.c:308)
==1786==    by 0x573DCCC: clone (clone.S:112)
==1786== 
==1786== LEAK SUMMARY:
==1786==    definitely lost: 24,000 bytes in 1,000 blocks
==1786==    indirectly lost: 0 bytes in 0 blocks
==1786==      possibly lost: 0 bytes in 0 blocks
==1786==    still reachable: 0 bytes in 0 blocks
==1786==         suppressed: 0 bytes in 0 blocks
==1786== 
==1786== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)
--1786-- 
--1786-- used_suppression:      2 dl-hack3-cond-1
==1786== 
==1786== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)
注:

  • 如果更改创建的线程数,则泄漏的块数与线程数匹配
  • 如果GCC是这样实现的,那么代码的结构可能允许资源重用(即泄漏的块)
  • 从valgrind stacktrace中,thread_local.cc:34是一行:
    thread_local Foo Foo
  • 由于sleep_for()调用,程序运行大约需要10秒左右

你知道这个内存泄漏是在GCC中,是我的配置选项导致的,还是我的程序中有错误吗?

尝试删除本地线程并使用以下代码

    void
    hello_world_thread()
    {
        Foo foo;

        // must access, or the thread local variable may not be instantiated
        foo.hello_world();

        // keep the thread around momentarily
        std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
    }
hello\u world\u线程中的foo应该位于每个线程的本地堆栈中。因此,每个线程都将维护自己的foo副本。不需要显式地将其标记为thread_local。当您有静态或命名空间级别的变量,但希望每个变量为每个线程维护自己的副本时,应该在上下文中使用线程\本地

问候
Kajal

泄漏似乎来自动态初始化

下面是一个带有
int
的示例:

thread_local int num=4; //static initialization
最后一个例子没有泄漏。我试了两条线,一点也不漏

但现在:

int func()
{
    return 4;
}
thread_local int num2=func(); //dynamic initialization
这一个漏水!通过2个线程,它给出了
总堆值:8个alloc,6个free,分配了428个字节

我建议您使用以下变通方法:

thread_local Foo *foo = new Foo; //dynamic initialization
不要忘记在线程执行结束时要执行的操作:

delete foo;
但最后一个例子是一个问题:如果线程在删除之前出错退出怎么办?再次泄漏


似乎没有什么好的解决办法。也许我们应该向
g++
开发人员报告这一点

这是一个完美组合的问题的光辉例子,做得很好。为什么要使用
静态
?修复。我重新运行了valgrind,但不幸的是泄漏仍然存在。@user2224952我已将代码简化为一个显示完全相同行为的最小示例。不幸的是,ideone不使用g++-4.8,但是您可以轻松地复制代码。删除Foo的析构函数可以解决这个问题。@stefan哦,删除析构函数实际上不是一个解决方案,是吗?如果您希望在线程退出时执行任何类型的清理,那么这是相当有限的。该解决方案还限制thread_local类的数据成员类型。例如,删除用户指定的析构函数,然后添加一个std::string成员,泄漏就回来了。实际的用例可能更像:
Foo&Foo(){thread\u local Foo Foo;return Foo;}void hello\u world\u thread(){if(some_condition){Foo().hello_world();}
只有在线程引用时才会实例化(并销毁)线程本地对象。似乎我在错误的地方最小化了我的代码示例,并在可能我不应该的地方增加了复杂性(即线程构造行为)。我为此问题创建了一个错误报告: