【问题标题】:Issue with std::thread when using g++ in 32-bit MinGW 4.8.0在 32 位 MinGW 4.8.0 中使用 g++ 时出现 std::thread 问题
【发布时间】:2015-09-15 07:49:14
【问题描述】:

背景 -- 我们开发C++11 代码并使用 gtest/gmock 编写单元测试。这是在 Windows 服务器上使用 SCons 和 MinGW 中的 g++ 构建的。在执行单元测试时,我们开始偶尔遇到问题:静默退出、预期错误、异常弹出……没有明显的模式或共性,也不容易重现。最终,一位同事将其范围缩小到一个明显的线程加入的情况,甚至没有开始执行其有效负载功能。在这种情况下,没有例外或类似情况。由于未达到预期,测试完全失败。然后我做了一个更简单的测试用例,既不涉及我们的代码库也不涉及 gtest/gmock。

BRIEF QUESTION -- 考虑以下代码 sn-p:

bool flag(false);
std::thread worker( [&] () { flag = true; } );
worker.join();
assert(flag);

当执行一次时,这似乎工作正常。 “一次”是指在测试可执行文件中一次。然后这个可执行文件从一个命令文件中重复运行多次。

但是,当在测试本身中重复执行时,上述断言通常会失败;有时在第二次重复时,有时在数千次重复后。

似乎 g++ std::thread 在 MinGW (4.8.0/32) 下表现不佳——线程已成功创建(即没有异常),它是可连接的,并且可以连接。但是,在某些情况下,它的有效载荷功能从未执行过。 -- 我知道 MinGW 没有完整的 POSIX pthreads,我已经看过 Using threads with MinGW?pthread_create not enough spaceMinGW and std::thread 等,但无济于事。我们确实使用静态链接(出于不同的原因),我还找到了https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57740

这一切都指向线程实现中的竞争条件。在测试中同时设置布尔标志 volatile 并关闭优化 (-O0) 没有任何区别。

我们目前在 32 位 MinGW 版本 4.8.0 中使用 g++(从 QT5.1 安装开箱即用),现在正在考虑迁移到一个不同的工具链(例如 Linux 机器上的 gcc/g++),或者至少升级到更高版本的 MinGW,如果有迹象表明这个问题可能已得到修复。

这是 MinGW 上 std::thread 的已知问题吗? 是否有任何修复或解决方法?(我的意思是一般修复。我已经实施了一些似乎可行但我不喜欢的解决方案。)

FULL DETAILS -- 执行下面的代码我们注意到:

[A] 运行下面的代码(到目前为止)执行永远不会在 #2 测试失败。 [预期]

[B] 但是,在 #4 的测试经常失败(在不同次数的重复之后,包括仅两次(!)重复;虽然有时需要数千测试失败)。 [意外]

[C] 仅在 #1 启用等待会导致 #4 出现 更多 条件失败( #2 的测试仍然没有失败)。 [意外]

[D] 仅在 #3 处启用等待使 #2#4 处的测试都成功. [嗯...]

使用 [D] “修复”并在 数千次​​trong> 重复之后,我已经看到(到目前为止两次)害怕 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

编辑

  • 正如@TC 指出的那样,使用 bool 是不正确的。最初,我使用atomic_bool,其行为与上述相同。然后我错误地将示例“简化”为 bool。
  • 顺便说一句,仅使用 yield 而不检查 #1 和 #3 的标志是足够的。

【问题讨论】:

  • 在线程创建或线程连接中似乎缺少内存屏障或优化屏障。
  • 看来这是一个错误。使用您提供的代码,我在 MinGW 4.8.0 上得到完全相同的结果。我也用在线编译器(IdeOne 和melpon.org/wandbox)试过这个,没有任何问题
  • 我有逐个案例的解决方法,但不是一般修复(目前在应用程序方面很好)。想法?
  • 旁注:#1 和 #3 都会引发数据竞争,因此行为未定义。
  • 你是对的。我起初使用 atomic_bool,但后来(错误地)简化为简单的 bool。顺便说一句,我与原子版本的行为完全相同。

标签: multithreading c++11 mingw stdthread


【解决方案1】:

非常感谢您提供非常详细的分析和出色的示例!
我用 x86_64-w64-mingw32-g++ (GCC) 4.8.2 检查了这个例子,
标志: -c -pipe -fno-keep-inline-dllexport -m64 -g -frtti -Wall -Wextra -fexceptions -mthreads

Windows 7 下运行,带有标志 -std=c++0x强>
每次都在第二次很早就失败了(循环迭代 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)第二次失败。


没有使用优化。 测试在 VirtualBox 中完成,干净安装 Windows 7/10,无需更新。
测试可执行文件需要库:

  • libstdc++-6.dll
  • libwinpthread-1.dll
  • libgcc_s_sjlj-1.dll

所以它在 Windows 10 下肯定要稳定得多,但并非完美无缺。
在 Windows7 下使用 c++11 而不是 c++0x 可能不太稳定。但我进行的测试太少,无法确定这一点。

有人尝试过更新版本的 MinGW 吗?

【讨论】:

    猜你喜欢
    • 2013-07-07
    • 2012-05-23
    • 1970-01-01
    • 2014-11-25
    • 1970-01-01
    • 2021-03-07
    • 1970-01-01
    • 2010-12-11
    • 1970-01-01
    相关资源
    最近更新 更多