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 实际上并未 分配 (也称为 提交 ),而是 保留。请参阅 VirtualAlloc 和 MEM_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? 警告不要使用数千个线程。我同意出于各种原因这样做是有问题的;我无意认可这种做法,我只是在解释一个必要的要素。
我有一个示例 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;
}
结果如下:
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 实际上并未 分配 (也称为 提交 ),而是 保留。请参阅 VirtualAlloc 和 MEM_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? 警告不要使用数千个线程。我同意出于各种原因这样做是有问题的;我无意认可这种做法,我只是在解释一个必要的要素。