5

標準テンプレート ライブラリを多用する C++ (VS2010) で記述されたマルチスレッド Win32 サービスがあります。プログラムのビジネス ロジックは適切に動作しますが、タスク マネージャー (またはリソース マネージャー) を見ると、プログラムはふるいのようにメモリ リークを起こしています。

1 秒あたり平均約 16 の同時リクエストのテスト セットがあります。プログラムを最初に起動すると、1.5Mb 程度の RAM を消費します。完全なテスト実行 (12 ~ 15 分かかります) の後、メモリ消費量は 12Mb 近くになります。通常、これは一度実行して終了するプログラムでは問題になりませんが、このプログラムは継続的に実行することを目的としています。本当に悪い。

問題を絞り込むために、250 ミリ秒ごとに 1 回の割合でワーカー スレッドをスピンオフする非常に小さなテスト アプリケーションを作成しました。ワーカー スレッドはマップを作成し、疑似乱数データを入力してマップを空にし、終了します。このプログラムも同様にメモリリークを起こしますので、STL が期待通りにメモリを解放しないことが問題だと思います。

リークを検索するために VLD を試してみたところ、修正したいくつかが見つかりましたが、それでも問題は残ります。Hoard を統合しようとしましたが、実際には問題が悪化しました (適切に統合していない可能性がありますが、方法がわかりません)。

そこで、次の質問をしたいと思います。マルチスレッド環境で STL を使用してメモリ リークしないプログラムを作成することは可能ですか? この 1 週間で、私はこのプログラムに 200 以上の変更を加えました。変更の結果をプロットしましたが、それらはすべて同じ基本プロファイルを持っています。このアプリケーションの開発を非常に簡単にした STL の利点をすべて削除する必要はありません。時代遅れになったようにメモリをリークすることなく、このアプリを動作させる方法についての提案を心から感謝します.

助けてくれてありがとう!

PS 検査/個人的な教育のために、メモリテストのコピーを投稿しています。

#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 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            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;
}
4

2 に答える 2

1

_beginthreadex()std ライブラリ (MS に関する限り、C ランタイムを含む) を使用する場合に使用します。また、std ランタイム サブアロケータで、特にこのようなより大きな要求を継続的に優先するように設計されたコードで、ある程度の断片化が発生します。

MS ランタイム ライブラリには、メモリ リクエストをデバッグし、適切なアルゴリズムを使用して明確に明らかなものが何も表示されないことに確信が持てたら、確実なリークがあるかどうかを判断できる関数がいくつかあります。詳細については、デバッグ ルーチンを参照してください。

最後に、あなたが書いたテスト ジグに次の変更を加えました。

  1. シャットダウン後のメモリ リークでデバッグ ウィンドウをスパムするために、適切な _Crt レポート モードをセットアップします。
  2. 最大数のスレッドを常に MAXIMUM_WAIT_OBJECTS で実行するようにスレッド起動ループを変更しました (WIN32 では現在 64 ハンドルとして定義されています)。
  3. プログラムの終了時にダンプするときに、CRT が実際にそれをキャッチすることを示すために、意図的にリークされた char 配列の割り当てを投入しました。
  4. コンソールのキーボード操作をなくしました。実行するだけです。

出力ログを見ると、これが理解できると思います。注: 適切なダンプを作成するには、デバッグ モードでコンパイルする必要があります。

#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 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            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;
}

出力デバッグ ウィンドウ

注: 2 つのリークが報告されています。1 つは std::string の割り当てで、もう 1 つはメッセージのコピーを保持していた std::string 内のバッファーです。

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.
于 2012-12-06T01:04:36.853 に答える
0

大規模なアプリケーションをデバッグするのは簡単な作業ではありません。
サンプルは、何が起こっているかを示すための最良の選択ではありません。
実際のコードの 1 つの断片がより適切に推測できます。
もちろんそれは不可能なので、私の提案は、すべての構造で挿入と削除の制御を含め、可能な限り最大のログを使用することです。この情報にカウンターを使用します。何かが疑われる場合は、すべてのデータのダンプを作成して、何が起こっているのかを理解します。アプリケーションへの影響が少なくなるように、情報を保存するために非同期で作業するようにしてください。これは簡単な作業ではありませんが、挑戦を楽しみ、C/C++ でのプログラミングがさらに好きな人にとっては、乗り物になるでしょう。
持続性とシンプルさが目標であるべきです。
幸運を

于 2012-12-06T01:49:59.700 に答える