【问题标题】:c++ Windows 32bit malloc() return NULL when opening many threadsc++ Windows 32bit malloc()打开多个线程时返回NULL
【发布时间】:2021-11-10 10:32:39
【问题描述】:

我有一个示例 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 位 Debug 运行它时,它将运行以下结果:

程序在内存不足之前可以使用近 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 将返回 NULL
谁能帮助解释为什么程序在内存不足之前不能使用高达 2Gb 的内存?
是不是说创建这么多线程会导致内存泄漏?

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

【问题讨论】:

  • 不确定这是否有帮助,但 CreateThread 文档说“进程可以创建的线程数受可用虚拟内存的限制。默认情况下,每个线程都有 1 兆字节的堆栈空间。”跨度>
  • 我想知道是否有任何工具可以测量并显示上面创建的线程导致内存不足
  • 你确定看懂任务管理器显示的图吗?
  • @IInspectable,不确定。但是我认为CreateThread分配的一些隐藏内存/虚拟内存不会显示在任务管理器中,对吗?

标签: c++ windows multithreading memory malloc


【解决方案1】:

正如@macroland 的评论中所指出的,这里发生的主要事情是每个线程为其堆栈消耗 1 MiB(请参阅 MSDN CreateThreadThread Stack Size)。你说malloc 返回NULL 一旦你直接分配的总量达到200 MB。由于您一次分配 131125 个字节,即 200 MB / 131125 B = 1525 个线程。它们的累积堆栈空间约为 1.5 GB。添加 200 MB 的 malloc 内存是 1.7 GB,其余的可能是杂项开销。

那么,为什么任务管理器不显示这个?因为整个 1 MiB 的线程堆栈空间实际上并未分配(也称为已提交),而是保留。请参阅 VirtualAllocMEM_RESERVE 标志。地址空间已保留用于扩展至 1 MiB,但最初仅分配 64 KiB,任务管理器仅计算后者。但是保留的内存在保留解除之前不会被malloc单方面重新利用,所以一旦可用地址空间用完,它就必须返回NULL

什么工具可以显示这个?我不知道现成的任何东西(即使Process Explorer 似乎也没有显示保留内存的计数)。我过去所做的是编写自己的小程序,使用VirtualQuery 枚举整个地址空间,包括保留范围。我建议你也这样做;它不需要编写太多代码,并且在为 32 位 Windows 编码时非常方便,因为 2 GiB 地址空间很容易变得狭窄(DLL 是一个明显的原因,但默认的 malloc 也会留下意想不到的保留以响应某些分配模式,即使你free 一切)。

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


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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-14
  • 2016-05-24
  • 2012-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多