I have a multi-threaded Win32 service written in C++ (VS2010) that makes extensive use of the standard template library. The business logic of the program operates properly, but when looking at the task manager (or resource manager) the program leaks memory like a sieve.
I have a test set that averages about 16 simultaneous requests/second. When the program is first started up it consumes somewhere in the neighborhood of 1.5Mb of ram. After a full test run (which take 12-15 minutes) the memory consumption ends up somewhere near 12Mb. Normally, this would not be a problem for a program that runs once and then terminates, but this program is intended to run continuously. Very bad, indeed.
To try and narrow down the problem, I created a very small test application that spins off worker threads at a rate of once every 250ms. The worker thread creates a map and populates it with pseudo-random data, empties the map, and then exits. This program, too, leaks memory in like fashion, so I'm thinking that the problem is with the STL not releasing the memory as expected.
I have tried VLD to search for leaks and it has found a couple which I have remedied, but still the problem remains. I have tried integrating Hoard, but that has actually made the problem worse (i'm probably not integrating it properly, but i can't see how).
So I would like to pose the following question: is it possible to create a program that uses the STL in a multi-threaded environment that will not leak memory? Over the course of the last week I have made no less than 200 changes to this program. I have plotted the results of the changes and they all have the same basic profile. I don't want to have to remove all of the STL goodness that has made developing this application so much easier. I would earnestly appreciate any suggestions on how I can get this app working without leaking memory like it's going out of style.
Thanks again for any help!
P.S. I'm posting a copy of the memory test for inspection/personal edification.
#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;
}
Use _beginthreadex()
when using the std library (includes the C runtime as far as MS is concerned). Also, you're going to experience a certain amount of fragmentation in the std runtime sub-allocator, especially in code designed to continually favor larger and larger requests like this.
The MS runtime library has some functions that allow you to debug memory requests and determine if there is a solid leak once you have a sound algorithm and are confident you don't see anything glaringly obvious. See the debug routines for more information.
Finally, I made the following modifications to the test jig you wrote:
Hopefully this will make sense when you see the output log. Note: you must compile in Debug mode for this to make any proper dump for you.
#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;
}
Output Debug Window
Note: there are two leaks reported. One is the std::string allocation, the other is the buffer within the std::string that held our message copy.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With