繁体   English   中英

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

[英]Data races, UB, and counters in 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
}

不幸的是,如果多个线程可以调用doit没有某种形式的同步,并发读-修改-写入num_times_done_it可能是一个数据的比赛,因此整个程序的行为将是不确定的。 此外,如果report_stats可以同时使用称为doit缺席任何同步,还有线程修改之间的另一数据竞争num_times_done_it和线程报告其价值。

通常,程序员只想要尽可能少的开销来调用doit的次数。

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

明显的非解决方案:

  • 原子论,我所知道的任何内存顺序,在这里“尽可能少的开销”失败(原子增量可能比普通增量贵得多),而在“大多数正确”(通过完全正确)过度交付。
  • 我不相信在混合中抛出volatile会使数据num_times_done_it正常,所以用volatile int num_times_done_it替换num_times_done_it的声明并不能解决任何问题。
  • 有有每个线程独立的柜台,把它们加起来在尴尬的解决方案report_stats ,但这并不解决之间的数据争doitreport_stats 此外,它很混乱,它假设更新是关联的,并不真正适合Hogwild!的用法。

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

编辑 :似乎我们可以使用memory_order_relaxed以稍微间接的方式执行此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
}

但是, 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)

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)

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

分别计算每个线程,并在线程加入后总结。 对于中间结果,您也可以在两者之间进行总结,但结果可能会关闭。 这种模式也更快。 您可以将它嵌入到线程的基本帮助器类中,这样如果您经常使用它,就可以使用它。

并且 - 取决于编译器和平台,原子并不那么昂贵(参见Herb Sutters“原子武器”谈话http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter -atomic-Weapons-1-of-2 )但在你的情况下,它会产生缓存问题,所以这是不可取的。

似乎memory_order_relaxed技巧是正确的方法。

英特尔的Dmitry Vyukov撰写的这篇博文首先回答了我的问题,并继续列出memory_order_relaxed storeload为正确的选择。

我仍然不确定这是否真的好; 特别是, N3710让我怀疑我是否曾首先理解memory_order_relaxed

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM