【发布时间】:2014-08-10 09:06:05
【问题描述】:
我看了(大部分)Herb Sutter's the atmoic<> weapons video,我想用样本内的循环来测试“条件锁”。显然,虽然(如果我理解正确的话)C++11 标准说下面的示例应该正常工作并且顺序一致,但事实并非如此。
在您继续阅读之前,我的问题是:这是正确的吗?编译器坏了吗?我的代码是否损坏 - 我是否有错过的比赛条件?如何绕过这个?
我在 3 个不同版本的 Visual C++ 上进行了尝试:VC10 专业版、VC11 专业版和 VC12 Express(== Visual Studio 2013 Desktop Express)。
以下是我用于 Visual Studio 2013 的代码。对于其他版本,我使用 boost 而不是 std,但想法是一样的。
#include <iostream>
#include <thread>
#include <mutex>
int a = 0;
std::mutex m;
void other()
{
std::lock_guard<std::mutex> l(m);
std::this_thread::sleep_for(std::chrono::milliseconds(2));
a = 999999;
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << a << "\n";
}
int main(int argc, char* argv[])
{
bool work = (argc > 1);
if (work)
{
m.lock();
}
std::thread th(other);
for (int i = 0; i < 100000000; ++i)
{
if (i % 7 == 3)
{
if (work)
{
++a;
}
}
}
if (work)
{
std::cout << a << "\n";
m.unlock();
}
th.join();
}
总结一下代码的思路:全局变量a被全局互斥锁m保护。假设没有命令行参数 (argc==1),运行 other() 的线程是唯一应该访问全局变量 a 的线程。
程序的正确输出是打印999999。
但是,由于编译器循环优化(使用寄存器进行循环内增量并在循环结束时将值复制回a),即使不应该由程序集修改a到。
这发生在所有 3 个 VC 版本中,尽管在 VC12 的这个代码示例中,我不得不对 sleep() 进行一些调用以使其中断。
这是一些汇编代码(本次运行中a的地址是0x00f65498):
循环初始化 - 来自 a 的值被复制到 edi
27: for (int i = 0; i < 100000000; ++i)
00F61543 xor esi,esi
00F61545 mov edi,dword ptr ds:[0F65498h]
00F6154B jmp main+0C0h (0F61550h)
00F6154D lea ecx,[ecx]
28: {
29: if (i % 7 == 3)
在条件内递增,并在循环后无条件复制回a的位置
30: {
31: if (work)
00F61572 mov al,byte ptr [esp+1Bh]
00F61576 jne main+0EDh (0F6157Dh)
00F61578 test al,al
00F6157A je main+0EDh (0F6157Dh)
32: {
33: ++a;
00F6157C inc edi
27: for (int i = 0; i < 100000000; ++i)
00F6157D inc esi
00F6157E cmp esi,5F5E100h
00F61584 jl main+0C0h (0F61550h)
32: {
33: ++a;
00F61586 mov dword ptr ds:[0F65498h],edi
34: }
程序的输出是0。
【问题讨论】:
-
代码看起来不错。我看不到数据竞赛。
-
@JAB C++
volatile,不像Javavolatile,与多线程无关。 -
@JAB 它不会为您提供重新排序保证,除非仅访问该变量。真的不是适合这项工作的工具。
-
我认为问题的标题应该是关于“仍然引入数据竞争”,而不是关于“顺序一致性”。
-
@Hans 什么错误?如果
work他在启动另一个线程之前获取了互斥锁,并且仅在持有锁的同时对其进行写入。如果!work主线程从不写入或读取a。
标签: c++ multithreading visual-c++ concurrency compiler-optimization