2013-07-16 28 views
12

Valgrind sta riportando blocchi trapelato, apparentemente uno per thread, nel codice seguente: versionePerdita di memoria in gcc 4.8.1 quando si utilizza thread_local?

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

Compiler:

$ g++ --version 
g++ (GCC) 4.8.1 
Copyright (C) 2013 Free Software Foundation, Inc. 
This is free software; see the source for copying conditions. There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

opzioni GCC di build:

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

opzioni di compilazione del programma:

g++ -std=gnu++11 -Og -g3 -Wall -Wextra -fno-omit-frame-pointer thread_local.cc 
Versione

valgrind:

$ valgrind --version 
valgrind-3.8.1 

opzioni Valgrind:

valgrind --leak-check=full --verbose ./a.out > /dev/null 

Tail-end di uscita 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) 

Costruttori e distruttori sono stati eseguiti una sola volta per ogni thread:

$ ./a.out | grep 'Foo::Foo' | wc -l 
1000 

$ ./a.out | grep hello_world | wc -l 
1000 

$ ./a.out | grep 'Foo::~Foo' | wc -l 
1000 

Note:

  • Se si modifica il numero di thread creati, il numero di blocchi trapelate corrisponde al numero di thread.
  • Il codice è strutturato in modo tale che potrebbe consentire il riutilizzo delle risorse (ad esempio il blocco trapelato) se GCC fosse implementato in tal modo.
  • Dal stacktrace valgrind, thread_local.cc:34 è la linea: thread_local Foo foo;
  • causa della chiamata sleep_for(), l'esecuzione del programma richiede circa 10 secondi o così.

Qualche idea se questa perdita di memoria è in GCC, un risultato delle mie opzioni di configurazione, o c'è qualche bug nel mio programma?

+3

Questo è un esempio brillante di una domanda perfettamente composta, ben fatta. – GManNickG

+0

Perché usi 'static'? – stefan

+0

corretto. Ho rielaborato Valgrind, ma sfortunatamente la perdita esiste ancora. – user2224952

risposta

2

Sembra che la perdita viene dal di inizializzazione dinamica .

Ecco un esempio con una int:

thread_local int num=4; //static initialization 

L'ultimo esempio non perde. L'ho provato con 2 thread e nessuna perdita.

Ma ora:

int func() 
{ 
    return 4; 
} 
thread_local int num2=func(); //dynamic initialization 

Questa perdita! Con 2 capi che dà total heap uage: 8 allocs, 6 frees, 428 bytes allocated ...

vorrei suggerire di utilizzare una soluzione del tipo:

thread_local Foo *foo = new Foo; //dynamic initialization 

Non dimenticare alla fine dell'esecuzione filo di fare:

delete foo; 

Ma l'ultimo esempio come un problema: Cosa succede se il thread esce con errore prima dell'eliminazione? ancora una volta Leak ...

Sembra che non v'è alcuna grande soluzione. Forse dovremmo segnalarlo agli sviluppatori di g++ a riguardo?

+0

Ho creato una segnalazione di bug per questo problema: [collegamento] (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57914) – user2224952

0

provare a rimuovere thread_local e utilizzando il seguente codice

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

foo all'interno hello_world_thread dovrebbe essere nella pila locale per ogni thread. quindi ogni thread manterrà la propria copia di foo. non è necessario contrassegnarlo esplicitamente come thread_local. Un thread_local dovrebbe essere usato in un contesto quando hai qualcosa come statico o variabile del livello del namespace ma vuoi che ogni variabile mantenga la propria copia per ogni thread.

saluti Kajal

+0

Il caso d'uso effettivo potrebbe essere qualcosa di più: 'Foo & foo() { thread_local Foo foo; ritorno foo; } vuoto hello_world_thread() { se (some_condition) { foo() hello_world().; }} ' Il filo oggetto locale sarebbe istanziata (o distrutto) se fa riferimento a un filo. Sembra che ho ridotto al minimo il mio esempio di codice nel posto sbagliato e aggiunto complessità dove forse non avrei dovuto (cioè il comportamento di costruzione del thread). – user2224952