为什么Win32 api _beginthreadex / CreateThread在使用CRT时会泄漏?

时间:2015-11-11 00:58:44

标签: c++ multithreading memory-leaks

我在使用_beginthreadex或CreateThread创建的线程上执行以下函数:

static volatile LONG g_executedThreads = 0;
void executeThread(int v){
   //1. leaks: time_t tt = _time64(NULL);
   //2. leaks: FILETIME ft; GetSystemTimeAsFileTime(&ft);
   //3. no leak: SYSTEMTIME stm; GetSystemTime(&stm);

   InterlockedAdd(&g_executedThreads, 1); // count nr of executions
 }

当我取消注释任何行 1。(crt call)或 2。(win 32 api call)时,线程泄漏,_begintreadex的下一次调用将失败(GetLastError) - >返回错误(8) - > 没有足够的存储空间可用于处理此命令)。 当_beginthreadex开始失败时,Process Explorer报告的内存: 私人 130 Mb ,虚拟 150 Mb

但如果我只取消注释 3。(其他赢得32 api调用)没有发生泄漏,并且在100万个线程之后没有失败。此处记忆的内存为私有 1.4 Mb ,虚拟 25 Mb 。而且这个版本的运行速度非常快(100万个线程的速度为20秒,而第一个版本的速度为300秒的60秒)。

我已经使用Visual Studio 2013测试过(see here the test code),编译了x86(调试和发布)并在Win 8.1 x64上运行;创建30000个线程后_beginthreadex开始失败(大多数调用);我想提一下,同时运行的线程在100以下。

更新2

我假设最多100个线程基于控制台输出(预定aprox等于已完成)并且“线程”选项卡中的Process Explorer未报告超过10个线程_)  这是控制台输出(没有WaitForSingleObject,原始代码):

step:0, scheduled:1, completed:1
step:5000, scheduled:5001, completed:5000
...
step:25000, scheduled:25001, completed:24999
step:30000, scheduled:30001, completed:30001
 _beginthreadex failed. err(8); errno(12). exiting ...
step:31701, scheduled:31712, completed:31710

rerun loop:
step:0, scheduled:31713, completed:31711
_beginthreadex failed. err(8); errno(12). exiting ...
step:6, scheduled:31719, completed:31716

基于@SHR& @HarryJohnston建议我一次安排了64个线程,等待所有完成,(see updated code here),但行为是一样的。注意我已尝试过一次单线程,但失败是偶发的。预留堆栈大小也是64K! 这是新的计划功能:

static unsigned int __stdcall _beginthreadex_wrapper(void *arg) {
    executeThread(1);
    return 0;
}
const int maxThreadsCount = MAXIMUM_WAIT_OBJECTS;
bool _beginthreadex_factory(int& step) {
    DWORD lastError = 0;

    HANDLE threads[maxThreadsCount];
    int threadsCount = 0;
    while (threadsCount < maxThreadsCount){
        unsigned int id;
        threads[threadsCount] = (HANDLE)_beginthreadex(NULL,
            64 * 1024, _beginthreadex_wrapper, NULL, STACK_SIZE_PARAM_IS_A_RESERVATION, &id);
        if (threads[threadsCount] == NULL) {
            lastError = GetLastError();
            break;
        }
        else threadsCount++;
    }

    if (threadsCount > 0) {
        WaitForMultipleObjects(threadsCount, threads, TRUE, INFINITE);
        for (int i = 0; i < threadsCount; i++) CloseHandle(threads[i]);
    }

    step += threadsCount;
    g_scheduledThreads += threadsCount;

    if (threadsCount < maxThreadsCount) {
        printf("    %03d sec: step:%d, _beginthreadex failed. err(%d); errno(%d). exiting ...\n", getLogTime(), step, lastError, errno);
        return false;
    }
    else return true;
}

以下是在控制台上打印的内容:

000 sec: step:6400, scheduled:6400, completed:6400
003 sec: step:12800, scheduled:12800, completed:12800
007 sec: step:19200, scheduled:19200, completed:19200
014 sec: step:25600, scheduled:25600, completed:25600
022 sec: step:32000, scheduled:32000, completed:32000
023 sec: step:32358, _beginthreadex failed. err(8); errno(12). exiting ...
sleep 5 seconds
028 sec: step:32358, scheduled:32358, completed:32358
try to create 2 more times
028 sec: step:32361, _beginthreadex failed. err(8); errno(12). exiting ...
032 sec: step:32361, scheduled:32361, completed:32361
rerun loop: 1
036 sec: step:3, _beginthreadex failed. err(8); errno(12). exiting ...
sleep 5 seconds
041 sec: step:3, scheduled:32364, completed:32364
try to create 2 more times
041 sec: step:5, _beginthreadex failed. err(8); errno(12). exiting ...
045 sec: step:5, scheduled:32366, completed:32366
rerun loop: 2
056 sec: step:2, _beginthreadex failed. err(8); errno(12). exiting ...
sleep 5 seconds
061 sec: step:2, scheduled:32368, completed:32368
try to create 2 more times
061 sec: step:4, _beginthreadex failed. err(8); errno(12). exiting ...
065 sec: step:4, scheduled:32370, completed:32370

欢迎任何建议/信息。 感谢。

2 个答案:

答案 0 :(得分:1)

我猜你弄错了。 看看这段代码:

int thread_func(void* p)
{
     Sleep(1000);
     return 0;
}
int main()
{
    LPTHREAD_START_ROUTINE s = (LPTHREAD_START_ROUTINE)&thread_func;
    for(int i=0;i<1000000;i++)
    {
        DWORD id;
        HANDLE h = CreateThread(NULL,0, s,NULL,0,&id); 
        WaitForSingleObject(h,INFINITE);
    }
    return 0;
}

泄漏的线程只是因为你调用它而泄漏,所以等待不会改变一个东西,但是当你在性能监视器中看到它时,你会发现所有的线几乎都是不变的。

现在问问自己,当我删除WaitForSingleObject时会发生什么?

线程的创建比线程运行得快得多,因此您可以达到每个进程的线程限制或每个进程的内存限制。 请注意,如果您正在为x86进行编译,则内存限制为4GB,但只有2GB用于用户模式内存,另外2GB用于内核模式内存。如果你使用默认的堆栈大小(1MB)用于线程,并且程序的其余部分根本不使用内存(它永远不会发生,因为你有代码...),那么你是限制为2000个线程。在2GB完成之后,您无法创建更多线程,直到之前的线程结束。

所以,我的结论是你创建线程并且不等待,并且在一段时间之后,没有留下更多线程的内存。

您可以检查性能监视器是否属于这种情况,并检查每个进程的最大线程数。

答案 1 :(得分:1)

卸载防病毒软件后,无法再现故障(即使代码运行速度与其他方案 3。一样快)。