背景-我们开发C++11
代码,并使用gtest/gmock编写单元测试。这是在Windows服务器上使用SCons和g++
中的MinGW
构建的。执行单元测试时,我们偶尔会遇到一些问题:静默退出,期望错误,异常弹出窗口……没有明显的模式或通用性,并且不容易复制。最终,一位同事将其范围缩小到明显是在没有开始执行其有效载荷功能的情况下就加入了线程的情况。在这种情况下,没有异常(exception)或类似情况。由于未达到预期,测试仅失败了。然后,我制作了一个更简单的测试用例,既不涉及我们的代码库也不涉及gtest/gmock。
简要问题-考虑以下代码片段:
bool flag(false);
std::thread worker( [&] () { flag = true; } );
worker.join();
assert(flag);
当执行一次时,这似乎可以正常工作 。 “一次”是指在测试可执行文件中一次。然后,该可执行文件会从命令文件中多次重复运行。
但是,当在测试本身内重复执行反复时,上述声明通常会在失败时失败;有时是在第二次重复中,有时是在数千次重复之后。
在MinGW(4.8.0/32)下,似乎g++
std::thread
表现不佳-线程已成功创建(即无异常),它是可联接的,并且可以联接。但是,在某些情况下,其有效载荷功能从未执行过。 -我知道MinGW没有完整的POSIX pthreads,并且我已经查看了Using threads with MinGW?,pthread_create not enough space,MinGW and std::thread等,但都无济于事。我们确实使用了静态链接(出于不同的原因),我还发现了https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57740。这一切都指向线程实现中的竞争条件。在测试
volatile
中同时设置两个 bool 标志并关闭优化(-O0
)没有什么区别。当前,我们在 32位MinGW版本4.8.0 (QT5.1安装中的即用型)中使用 g++ (现在从QT5.1安装中立即使用),现在正在考虑转移到其他工具链(例如,Linux机器上的gcc/g++)或至少有迹象表明可能已解决此问题,请至少升级到更高版本的MinGW。
这是MinGW上std::thread的已知问题吗?是否有任何修复程序或解决方法? (我的意思是一般性修复。我已经实现了一些逐案解决方法,这些方法似乎可行,但我不喜欢它们。)
详细信息-执行下面的代码,我们注意到:
[A] 在#2 上运行下面的代码(到目前为止)永远不会使测试失败。 [预期的]
[B] 但是,在#4上的测试经常失败(在重复的次数不同之后,包括两次(!)重复;尽管有时测试失败前需要花费数千次)。 [意外]
[C] 在#1 上独家启用等待会导致#4 出现更多条件失败(在#2上进行测试仍然不会失败)。 [意外]
[D] 在#3上独家启用等待使#2 和#4 上的测试都成功。 [唔...]
使用[D]“修复”,并在重复数千次之后,我已经看到(到目前为止两次)可怕的R6016(-没有足够的空间容纳线程数据)。 (从某种意义上说,这是可以理解的,也许不必担心,只要在测试之间定期恢复线程资源并且测试不会背对背运行。)
请注意,在#1和#3处的“等待”只是为了说明-它们没有超时并且可能会挂起。
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <thread>
int main(int, char *[])
{
bool flag1(false);
assert(not flag1);
std::thread worker1( [&] () { flag1 = true; } );
assert(worker1.joinable());
// while (not flag1) { std::this_thread::yield(); } // #1: MAKES #4 FAIL MORE OFTEN
worker1.join();
if (not flag1) // #2: DOES NOT FAIL
{
puts("Oops on first!");
exit(EXIT_FAILURE);
}
bool flag2(false);
assert(not flag2);
std::thread worker2( [&] () { flag2 = true; } );
assert(worker2.joinable());
// while (not flag2) { std::this_thread::yield(); } // #3: MAKES #4 SUCCEED
worker2.join();
if (not flag2) // #4: SOMETIMES FAILS
{
puts("Oops on second!");
exit(EXIT_FAILURE);
}
puts("Both OKAY");
return EXIT_SUCCESS;
}
编译到test.exe中,可以使用以下命令重复运行以上测试:
@ECHO OFF
FOR /L %%i IN (1,1,1000000) DO (
ECHO __ %%i ________________________________________________________________________________ %%i __
test.exe
IF ERRORLEVEL 1 GOTO gameover
)
:gameover
编辑
atomic_bool
具有与上述相同的行为。然后,我错误地将示例“简化”为bool。 yield
而不检查#1和#3处的标志是,不足。 最佳答案
非常感谢您所做的非常详细的分析和出色的示例!
我已经使用x86_64-w 64 -mingw32-g++(GCC)4.8.2检查了此示例,标志:
-c -pipe -fno-keep-inline-dllexport -m64 -g -frtti -Wall -Wextra -fexceptions -mthreads在以下运行
带有标志-std = c++ 0x的Windows 7
每秒都相当早地失败(循环迭代293、805、1632、276)
带有标志-std = c++ 11的Windows 7
它每次都相当早就失败了(循环迭代4、257、613、49)
带有标志-std = c++ 0x的Windows 10
一秒钟很长时间(循环迭代44924)后,它失败了。
带有标志-std = c++ 11的Windows 10
在很长一段时间(循环迭代7389、41907)之后,它失败了。
没有使用任何优化。
在带有Windows 7/10全新安装且没有更新的VirtualBox中进行的测试。
测试可执行文件需要以下库:
因此,它在Windows 10下绝对稳定得多,但并非完美无缺。
在Windows7下使用c++ 11而不是c++ 0x可能不太稳定。但是我进行的测试太少,无法确定这一点。
有人尝试过更新版本的MinGW吗?
关于multithreading - 在32位MinGW 4.8.0中使用g++时std::thread出现问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32580527/