在VS2010上使用STL的多线程Win32服务的内存问题

时间:2012-12-05 22:20:53

标签: visual-studio-2010 winapi service memory-leaks stl

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

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

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

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

所以我想提出以下问题:是否可以创建一个在不会泄漏内存的多线程环境中使用STL的程序?在过去的一周里,我对这个程序进行了不少于200次的更改。我已经绘制了更改的结果,它们都具有相同的基本配置文件。我不想删除使开发此应用程序变得更容易的所有STL优点。我会非常感谢任何有关如何让这个应用程序工作而不会泄漏内存的建议,就像它已经过时一样。

再次感谢您的帮助!

P.S。我正在发布一份内存测试副本,用于检查/个人启发。

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

2 个答案:

答案 0 :(得分:1)

使用std库时使用_beginthreadex()(就MS而言包括C运行时)。此外,您将在std运行时子分配器中遇到一定数量的碎片,尤其是在设计用于持续支持更大和更大请求的代码中。

MS运行时库有一些功能,允许您调试内存请求,并确定一旦您有一个合理的算法,是否存在可靠的泄漏,并确信您没有看到任何明显的东西。有关详细信息,请参阅the debug routines

最后,我对你写的测试夹具进行了以下修改:

  1. 设置正确的_Crt报告模式,以便在关机后发生任何内存泄漏时发送调试窗口。
  2. 修改了线程启动循环,以使最大线程数始终保持在MAXIMUM_WAIT_OBJECTS(WIN32-当前定义为64个句柄)
  3. 投入一个有目的的泄露char数组分配,以显示CRT实际上会在程序终止时倾倒它。
  4. 消除了控制台键盘交互。跑吧。
  5. 希望看到输出日志时这是有意义的。注意:您必须在调试模式下编译才能为您进行任何正确的转储。

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

答案 1 :(得分:0)

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