C++ 11中的数据竞争,UB和计数器

tmy*_*ebu 6 c++ multithreading memory-model c++11

以下模式在许多软件中很常见,这些软件想告诉用户它做了多少事情:

int num_times_done_it; // global

void doit() {
  ++num_times_done_it;
  // do something
}

void report_stats() {
  printf("called doit %i times\n", num_times_done_it);
  // and probably some other stuff too
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,如果多个线程可以在doit没有某种同步的情况下调用,则并发读取 - 修改 - 写入num_times_done_it可能是数据争用,因此整个程序的行为将是未定义的.此外,如果report_stats可以在doit没有任何同步的情况下同时调用,则在线程修改num_times_done_it和报告其值的线程之间存在另一个数据争用.

通常,程序员只想要doit尽可能少的开销来调用大多数正确的次数.

(如果你认为这个例子是微不足道的,Hogwild!比使用基本上这个技巧的数据无竞争随机梯度下降获得了显着的速度优势.而且,我相信Hotspot JVM正是这种无人看守,多线程访问共享计数器对于方法调用计数---虽然它是明确的,因为它生成汇编代码而不是C++ 11.)

明显的非解决方案:

  • 原子论,我所知道的任何内存顺序,在这里"尽可能少的开销"失败(原子增量可能比普通增量贵得多),而在"大多数正确"(通过完全正确)过度交付.
  • 我不相信折腾volatile到组合,使数据的比赛好了,更换的声明num_times_done_itvolatile int num_times_done_it不能解决任何事情.
  • 有一个尴尬的解决方案,每个线程有一个单独的计数器,并将它们全部添加report_stats,但这并不能解决doit和之间的数据竞争report_stats.此外,它很乱,它假设更新是关联的,并不真正适合Hogwild!的用法.

是否有可能在一个非平凡的多线程C++ 11程序中实现具有良好定义语义的调用计数器,而无需某种形式的同步?

编辑:似乎我们可以使用memory_order_relaxed以下方式稍微间接地执行此操作:

atomic<int> num_times_done_it;
void doit() {
  num_times_done_it.store(1 + num_times_done_it.load(memory_order_relaxed),
                          memory_order_relaxed);
  // as before
}
Run Code Online (Sandbox Code Playgroud)

但是,gcc 4.8.2在x86_64(使用-O3)上生成此代码:

   0:   8b 05 00 00 00 00       mov    0x0(%rip),%eax
   6:   83 c0 01                add    $0x1,%eax
   9:   89 05 00 00 00 00       mov    %eax,0x0(%rip)
Run Code Online (Sandbox Code Playgroud)

clang 3.4在x86_64上生成此代码(再次使用-O3):

   0:   8b 05 00 00 00 00       mov    0x0(%rip),%eax
   6:   ff c0                   inc    %eax
   8:   89 05 00 00 00 00       mov    %eax,0x0(%rip)
Run Code Online (Sandbox Code Playgroud)

我对x86-TSO的理解是这两个代码序列都禁止中断和有趣的页面保护标志,完全等同于单指令存储器incadd由简单代码生成的单指令存储器.这种使用是否memory_order_relaxed构成数据竞争?

tmy*_*ebu 0

看来,memory_order_relaxed诀窍是做到这一点的正确方法。

英特尔的德米特里·维尤科夫 (Dmitry Vyukov) 撰写的这篇博文首先准确回答了我的问题,然后列出了memory_order_relaxed storeload作为正确的选择。

我仍然不确定这是否真的可以;尤其是N3710让我怀疑自己是否曾经理解过memory_order_relaxed