c++ Windows 32bit malloc() return 打开多线程时为NULL

c++ Windows 32bit malloc() return NULL when opening many threads

我有一个示例 C++ 程序如下:

#include <windows.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    void * pointerArr[20000];
    int i = 0, j;
    for (i = 0; i < 20000; i++) {
        
        void * pointer = malloc(131125);
        if (pointer == NULL) {
            printf("i = %d, out of memory!\n", i);
            getchar();
            break;
        } 
        
        pointerArr[i] = pointer;
    }

    for (j = 0; j < i; j++) {
        free(pointerArr[j]);
    }

    getchar();
    return 0;
}

当我 运行 它与 Visual Studio 32 位调试时,它将 运行 具有以下结果:

该程序在内存不足之前可以使用近 2Gb 的内存。
这是正常行为。

但是,当我添加代码以在 for 循环内启动线程时,如下所示:

#include <windows.h>
#include <stdio.h>

DWORD WINAPI thread_func(VOID* pInArgs)
{
    Sleep(100000);
    return 0;

}

int main(int argc, char* argv[])
{
    void * pointerArr[20000];

    int i = 0, j;
    for (i = 0; i < 20000; i++) {

        CreateThread(NULL, 0, thread_func, NULL, 0, NULL);
        
        void * pointer = malloc(131125);
        if (pointer == NULL) {
            printf("i = %d, out of memory!\n", i);
            getchar();
            break;
        }

        pointerArr[i] = pointer;
    }

    for (j = 0; j < i; j++) {
        free(pointerArr[j]);
    }

    getchar();
    return 0;
}

结果如下: 内存仍然只有 200Mb 左右,但函数 malloc 将 return NULL.
谁能帮忙解释一下为什么程序在内存不足之前不能使用最多 2Gb 的内存?
是不是像上面这样创建很多线程会导致内存泄漏?

在我的实际应用中,当我创建大约 800 个线程时会出现此错误,“内存不足”时的 RAM 内存约为 300Mb。

正如@macroland 在评论中指出的那样,这里发生的主要事情是每个线程为其堆栈消耗 1 MiB(请参阅 MSDN CreateThread and Thread Stack Size)。你说 malloc returns NULL 一旦你直接分配的总数达到 200 MB。由于您一次分配 131125 个字节,即 200 MB / 131125 B = 1525 个线程。他们的累积堆栈 space 将约为 1.5 GB。添加 200 MB 的 malloc 内存是 1.7 GB,其他开销可能占其余部分。

那么,为什么任务管理器不显示这个?因为完整的 1 MiB 线程堆栈 space 实际上并未 分配 (也称为 提交 ),而是 保留。请参阅 VirtualAllocMEM_RESERVE 标志。地址 space 已预留至 1 MiB,但最初只分配了 64 KiB,任务管理器只计算后者。但是保留的内存不会被 malloc 单方面重新利用,直到保留被取消,所以一旦它用完可用地址 space,它必须 return NULL.

什么工具可以显示这个?我不知道现成的任何东西(甚至 Process Explorer does not seem show a count of reserved memory). What I have done in the past is write my own little routine that uses VirtualQuery 来枚举整个地址 space,包括保留范围。我建议你也这样做;代码不多,而且非常方便编码为 32 位 Windows 因为 2 GiB 地址 space 很容易变得拥挤(DLL 是一个明显的原因,但默认 malloc 也会留下意外的保留以响应某些分配即使你 free 一切都是模式)。

无论如何,如果要在 32 位 Windows 进程中创建数千个线程,请务必将非零值作为 dwStackSize 参数传递给 CreateThread,并将 STACK_SIZE_PARAM_IS_A_RESERVATION 作为 dwCreationFlags 传递。最小值为 64 KiB,如果您避免在线程中使用递归算法,这将足够了。


附录:在评论中,@iinspectable 引用 Raymond Chen 2005 年的博客 post Does Windows have a limit of 2000 threads per process? 警告不要使用数千个线程。我同意出于各种原因这样做是有问题的;我无意认可这种做法,我只是在解释一个必要的要素。