在Windows上,多个线程中使用malloc()/free()会导致崩溃

12

下面是简单的代码(包括在100个线程中运行malloc()/free()序列),在我尝试运行它时会在任何Windows操作系统上崩溃。

非常感谢您提供的任何帮助。

也许使用一些编译指令可以帮助解决问题?

我们在VS2017 Release/x64中构建可执行文件;在运行几分钟后,可执行文件会在我尝试的任何Windows平台上崩溃。

我也尝试过使用VS2015进行构建,但没有帮助。

同样的代码在Linux上运行良好。

实际上,问题比看起来更严重。在生产环境中,当用户呼叫数量超过某个值时,我们的服务器代码每天会无缘无故地崩溃多次。我们试图找出问题并创建最简单的解决方案来复制这个问题。

VS项目的存档在这里

VS表示命令行为:

/Yu"stdafx.h" /GS /GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl 
/Fd"x64\Release\vc140.pdb" /Zc:inline /fp:precise /D "NDEBUG"
/D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd
/Oi /MD /Fa"x64\Release\" /EHsc /nologo /Fo"x64\Release\" /Fp"x64\Release\MallocTest.pch"

代码:

#include "stdafx.h"
#include <iostream>
#include <thread>
#include <conio.h>

using namespace std;

#define MAX_THREADS 100

void task(void) {
    while (true) {
        char *buffer;
        buffer = (char *)malloc(4096);
        if (buffer == NULL) {
            cout << "malloc error" << endl;
        }
        free(buffer);
    }
}

int main(int argc, char** argv) {    
    thread some_threads[MAX_THREADS];

    for (int i = 0; i < MAX_THREADS; i++) {
        some_threads[i] = thread(task);
    }

    for (int i = 0; i < MAX_THREADS; i++) {
        some_threads[i].join();
    }

    _getch();
    return 0;
}

3
你是否正在使用线程安全版本的运行时库? - molbdnilo
1
@molbdnilo "你是否链接了线程安全版本的运行时库?"我猜是的,因为命令行中有/MD标志:/Yu"stdafx.h" /GS /GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Fd"x64\Release\vc140.pdb" /Zc:inline /fp:precise /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oi /MD /Fa"x64\Release" /EHsc /nologo /Fo"x64\Release" /Fp"x64\Release\MallocTest.pch" - peterg
3
我能够复现这个问题。即使使用 new,错误仍然会发生(实际上它仍然调用相同的 _malloc_base 函数)。调用堆栈以 ntdll.dll!RtlpLowFragHeapAllocFromContext(); tdll.dll!RtlpAllocateHeapInternal(); ucrtbase.dll!_malloc_base() 结束。 - user7860670
1
@molbdnilo在我的VC2017,Release x64上使用1000个线程时崩溃。 注意:根据文档,Malloc是线程安全的。 深入研究汇编代码可能会揭示更多信息。 - Zacharias
3
你让我的一天充满了阳光。在使用VC2017的100个线程时,我复现了这个问题。它显示HeapAlloc(在ntdll.dll中的某个地方)出现了访问冲突。这可能是某个愚蠢错误...或者你已经发现了微软C++或Windows系统的一个漏洞。 - Pavlo K
显示剩余30条评论
2个回答

4

这是Windows低碎片堆的一个问题。它在OS build 19041中进行了修复(即2020年5月更新)。


你有任何关于KB文章、Bug ID或类似的参考资料吗?一个类似的测试程序在安装了最新更新的Windows Server 2012R2和Windows Server 2016上仍然会崩溃,这个问题持续到2022年。 - Filip Konvička
很遗憾,这个信息是我在MSVC STL Discord服务器上从一位微软工程师那里得到的,此前在Stack Overflow上发布了这个主题。如果在WS2012和WS2016中仍然存在问题,我想这是因为修复程序从未被服务到这些版本,但它应该在WS2022中正常工作。 - Charles Milette
谢谢!根据我的测试,它可以在WS2008和WS2019上运行(我还没有试过WS2022)。 问题是,我们的客户有几个虚拟化的WS2016,并且我们不断遇到0xC0000374(堆损坏)应用程序崩溃,我们认为这个错误可能是一个可能的原因。(叹气) - Filip Konvička

1

你给出的小型可重现程序并未显示出编程错误,malloc()free() 应该是线程安全的,调用 cout 的方法也应该是线程安全的。该程序设计为永不停止,因此它似乎是在多线程环境下对 malloc() 进行压力测试的良好样例。

然而请注意,如果 malloc() 失败,则尝试将错误报告给 cout 是有问题的,因为它可能会对缓冲区进行进一步的 malloc() 调用。建议将错误报告到 cerr 或使 cout 不带缓冲区。无论如何,malloc() 失败都不应该导致崩溃,即使在流方法中也是如此。

看起来你在 VS 目标平台上链接的运行时库中发现了一个错误。跟踪程序在崩溃之前的内存使用情况会很有意义。内存使用量的稳步增加将表明运行时库存在某些问题。该程序每次仅分配 MAX_THREADS 个 4K 块,因此内存使用量应该保持相当低,低于2MB,包括现代实现的基于线程的缓存所带来的开销。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接