2012-12-05 5 views
5

Mam wielowątkową usługę Win32 napisaną w C++ (VS2010), która szeroko wykorzystuje standardową bibliotekę szablonów. Logika biznesowa programu działa poprawnie, ale patrząc na menedżera zadań (lub menedżera zasobów) program wyciekuje pamięć jak sito.Problemy z pamięcią w wielowątkowej usłudze Win32 używającej STL na VS2010

Mam zestaw testowy, który zawiera około 16 równoczesnych żądań/sekundę. Po pierwszym uruchomieniu program zużywa się gdzieś w okolicy 1,5 MB pamięci RAM. Po pełnym teście (trwającym 12-15 minut) zużycie pamięci kończy się w okolicach 12 Mb. Zwykle nie stanowiłoby to problemu dla programu, który uruchamia się raz, a następnie kończy, ale program ten ma działać nieprzerwanie. Bardzo źle.

Aby spróbować zawęzić problem, utworzyłem bardzo małą aplikację testową, która obraca wątki robocze z szybkością co 250 ms. Wątek roboczy tworzy mapę i zapełnia ją pseudolosowymi danymi, opróżnia mapę, a następnie kończy działanie. Ten program również przecieka pamięć w podobny sposób, więc myślę, że problem polega na tym, że STL nie zwalnia pamięci zgodnie z oczekiwaniami.

Próbowałem VLD, aby wyszukać nieszczelności i znalazłem parę, którą naprawiłem, ale nadal pozostaje problem. Próbowałem zintegrować Hoarda, ale to faktycznie pogorszyło problem (prawdopodobnie nie integruję go poprawnie, ale nie widzę jak).

Chciałbym postawić następujące pytanie: czy możliwe jest stworzenie programu wykorzystującego STL w środowisku wielowątkowym, które nie spowoduje wycieku pamięci? W ciągu ostatniego tygodnia dokonałem co najmniej 200 zmian w tym programie. Przedstawiłem wyniki zmian i wszystkie mają ten sam podstawowy profil. Nie chcę, aby usunąć całą dobroć STL, która znacznie ułatwiła rozwój tej aplikacji. Gorąco doceniam wszelkie sugestie na temat tego, jak mogę sprawić, by ta aplikacja działała bez przecieków pamięci, tak jak wychodzi z mody.

Jeszcze raz dziękuję za pomoc!

P.S. Publikuję kopię testu pamięci do inspekcji/osobistego montażu.

#include <string> 
#include <iostream> 
#include <Windows.h> 
#include <map> 

using namespace std; 

#define MAX_THD_COUNT 1000 

DWORD WINAPI ClientThread(LPVOID param) 
{ 
    unsigned int thdCount = (unsigned int)param; 

    map<int, string> m; 

    for (unsigned int x = 0; x < 1000; ++x) 
    { 
     string s; 

     for (unsigned int y = 0; y < (x % (thdCount + 1)); ++y) 
     { 
      string z = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 
      unsigned int zs = z.size(); 

      s += z[(y % zs)]; 
     } 

     m[x] = s; 
    } 

    m.erase(m.begin(), m.end()); 

    ExitThread(0); 

    return 0; 
} 

int main(int argc, char ** argv) 
{ 
    // wait for start 
    string inputWait; 
    cout << "type g and press enter to go: "; 
    cin >> inputWait; 

    // spawn many memory-consuming threads 
    for (unsigned int thdCount = 0; thdCount < MAX_THD_COUNT; ++thdCount) 
    { 
     CreateThread(NULL, 0, ClientThread, (LPVOID)thdCount, NULL, NULL); 

     cout 
      << (int)(MAX_THD_COUNT - thdCount) 
      << endl; 

     Sleep(250); 
    } 

    // wait for end 
    cout << "type e and press enter to end: "; 
    cin >> inputWait; 

    return 0; 
} 
+2

'm.erase (m.begin(), m.end())' będzie "m.clear()' w większości książek, ale ponieważ wygasa po powrocie proc mimo to, nie jestem pewien dlaczego jest nawet tam. Poza tym może wystąpić fragmentacja i/lub nieczyste zamknięcie CRT. W tym celu powinieneś używać '_beginthreadex()' dla swoich wątków, i * nie * 'ExitThread()' w drodze na zewnątrz. niech wątek proc wygaśnie przez-return. Co więcej, przeciekasz uchwyt wątku przy każdym wykonaniu. Powinien zostać zamknięty zaraz po 'CreateThread()', który aktualnie nie zapisuje nawet uchwytu. – WhozCraig

+0

Zapomniałem usunąć ExitThread() z postu. Próbowałem tego raz i nie zrobił nic, aby pomóc, więc szybko go usunąłem. Masz rację co do jasności, a ja użyłem obu wersji na wypadek, gdyby było coś ukrytego, czego mi brakowało. Przyjrzę się użyciu '_beginthreadex()', aby sprawdzić, czy to pomaga. Całkowicie przegapiłem zamknięcie uchwytu nici. Dzięki! –

+0

Zobacz tutaj http://support.microsoft.com/kb/104641/en-gb to może być nic. – James

Odpowiedz

1

Zastosowanie _beginthreadex() przy użyciu biblioteki std (Zawiera C wykonawczego MS ile dotyczy). Będziesz także doświadczał pewnej fragmentacji w podziale przydzielania standardowego czasu rzeczywistego, zwłaszcza w kodzie zaprojektowanym tak, aby stale faworyzować większe i większe żądania w ten sposób.

Biblioteka środowiska wykonawczego MS ma kilka funkcji, które umożliwiają debugowanie żądań pamięci i ustalenie, czy wystąpił trwały wyciek, gdy już masz algorytm dźwięku i jesteś pewien, że nie widzisz niczego jaskrawo oczywistego. Aby uzyskać więcej informacji, patrz the debug routines.

Wreszcie dokonała następujących zmian w przyrządzie testowym napisał:

  1. instalacji odpowiedniego trybu raport _Crt do spamowania okno debugowania z jakichkolwiek wycieków pamięci po wyłączeniu.
  2. modyfikacja pętli wątku startowego, aby utrzymać maksymalną liczbę wątków działa stale w MAXIMUM_WAIT_OBJECTS (Win32 zdefiniowane obecnie jako 64 uchwyty)
  3. wrzucił celowego rozdziału wyciekły tablicy char pokazania CRT będzie w rzeczywistości, zaczep podczas zrzutów po zakończeniu programu.
  4. Wyeliminowano interakcję klawiatury konsoli. Po prostu uruchom.

Mam nadzieję, że będzie to sensowne, gdy zobaczysz dziennik wyjściowy. Uwaga: aby to zrobić, musisz skompilować w trybie debugowania.

#include <windows.h> 
#include <dbghelp.h> 
#include <process.h> 
#include <string> 
#include <iostream> 
#include <map> 
#include <vector> 

using namespace std; 

#define MAX_THD_COUNT 250 
#define MAX_THD_LOOPS 250 

unsigned int _stdcall ClientThread(void *param) 
{ 
    unsigned int thdCount = (unsigned int)param; 
    map<int, string> m; 

    for (unsigned int x = 0; x < MAX_THD_LOOPS; ++x) 
    { 
     string s; 
     for (unsigned int y = 0; y < (x % (thdCount + 1)); ++y) 
     { 
      string z = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 
      size_t zs = z.size(); 
      s += z[(y % zs)]; 
     } 
     m[x].assign(s); 
    } 
    return 0; 
} 

int main(int argc, char ** argv) 
{ 
    // setup reporting mode for the debug heap. when the program 
    // finishes watch the debug output window for any potential 
    // leaked objects. We're leaking one on purpose to show this 
    // will catch the leaks. 
    int flg = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); 
    flg |= _CRTDBG_LEAK_CHECK_DF; 
    _CrtSetDbgFlag(flg); 

    static char msg[] = "Leaked memory."; 
    new std::string(msg); 

    // will hold our vector of thread handles. we keep this fully populated 
    // with running threads until we finish the startup list, then wait for 
    // the last set of threads to expire. 
    std::vector<HANDLE> thrds; 
    for (unsigned int thdCount = 0; thdCount < MAX_THD_COUNT; ++thdCount) 
    { 
     cout << (int)(MAX_THD_COUNT - thdCount) << endl; 
     thrds.push_back((HANDLE)_beginthreadex(NULL, 0, ClientThread, (void*)thdCount, 0, NULL)); 
     if (thrds.size() == MAXIMUM_WAIT_OBJECTS) 
     { 
      // wait for any single thread to terminate. we'll start another one after, 
      // cleaning up as we detected terminated threads 
      DWORD dwRes = WaitForMultipleObjects(thrds.size(), &thrds[0], FALSE, INFINITE); 
      if (dwRes >= WAIT_OBJECT_0 && dwRes < (WAIT_OBJECT_0 + thrds.size())) 
      { 
       DWORD idx = (dwRes - WAIT_OBJECT_0); 
       CloseHandle(thrds[idx]); 
       thrds.erase(thrds.begin()+idx, thrds.begin()+idx+1); 
      } 
     } 
    } 

    // there will be threads left over. need to wait on those too. 
    if (thrds.size() > 0) 
    { 
     WaitForMultipleObjects(thrds.size(), &thrds[0], TRUE, INFINITE); 
     for (std::vector<HANDLE>::iterator it=thrds.begin(); it != thrds.end(); ++it) 
      CloseHandle(*it); 
    } 

    return 0; 
} 

wyjścia Debug Okno

Uwaga: istnieją dwa przecieki zgłaszane. Jedna to alokacja std :: string, druga to bufor w std :: string, który trzymał naszą kopię wiadomości.

Detected memory leaks! 
Dumping objects -> 
{80} normal block at 0x008B1CE8, 8 bytes long. 
Data: <09  > 30 39 8B 00 00 00 00 00 
{79} normal block at 0x008B3930, 32 bytes long. 
Data: < Leaked memor> E8 1C 8B 00 4C 65 61 6B 65 64 20 6D 65 6D 6F 72 
Object dump complete. 
0

Nie jest to łatwe zadanie do debugowania dużych aplikacji.
Twoja próbka nie jest najlepszym wyborem, aby pokazać, co się dzieje.
Jeden fragment twojego prawdziwego kodu zgaduje lepiej.
Oczywiście nie jest to możliwe, więc moja sugestia brzmi: użyj maksymalnie możliwego logu, w tym kontroli wstawiania i usuwania we wszystkich strukturach. Użyj liczników do tych informacji. Kiedy podejrzewają, że coś robi zrzut wszystkich danych, aby zrozumieć, co się dzieje. Staraj się pracować asynchronicznie, aby zapisać informacje, aby mieć mniejszy wpływ na aplikację. To nie jest łatwe zadanie, ale dla każdego, kto lubi wyzwania i jeszcze bardziej kocha programowanie w C/C++, będzie to jazda.
Trwałość i prostota powinny być celem.
Powodzenia

Powiązane problemy