5

我有一个用 C++ (VS2010) 编写的多线程 Win32 服务,它广泛使用了标准模板库。程序的业务逻辑运行正常,但是在查看任务管理器(或资源管理器)时,程序像筛子一样泄漏内存。

我有一个测试集,平均每秒大约 16 个并发请求。当程序第一次启动时,它会消耗大约 1.5Mb 的内存。在完整的测试运行(需要 12-15 分钟)之后,内存消耗最终接近 12Mb。通常,对于运行一次然后终止的程序来说,这不是问题,但该程序旨在连续运行。非常糟糕,确实。

为了尝试缩小问题范围,我创建了一个非常小的测试应用程序,它以每 250 毫秒一次的速度分离工作线程。工作线程创建一个地图并用伪随机数据填充它,清空地图,然后退出。这个程序也以类似的方式泄漏内存,所以我认为问题在于 STL 没有按预期释放内存。

我尝试过 VLD 来搜索泄漏,它找到了一些我已经补救的问题,但问题仍然存在。我尝试过集成 Hoard,但这实际上使问题变得更糟(我可能没有正确集成它,但我看不出如何)。

所以我想提出以下问题:是否可以在不会泄漏内存的多线程环境中创建使用STL的程序?在上周的过程中,我对该程序进行了不少于 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 实际上会在程序终止时转储时捕获它。
  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;
}

输出调试窗口

注意:报告了两次泄漏。一个是 std::string 分配,另一个是 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

调试大型应用程序并非易事。
您的样本不是显示正在发生的事情的最佳选择。
您的真实代码的一个片段猜测得更好。
当然不可能,所以我的建议是:使用尽可能多的日志,包括所有结构中的插入和删除控件。使用此信息的计数器。当他们怀疑某些事情时,会转储所有数据以了解正在发生的事情。尝试异步工作以保存信息,以减少对您的应用程序的影响。这不是一件容易的事,但对于任何喜欢挑战并且更喜欢用 C/C++ 编程的人来说,这将是一件容易的事。
持久性和简单性应该是目标。
祝你好运

于 2012-12-06T01:49:59.700 回答