【问题标题】:Race condition for shared variable共享变量的竞争条件
【发布时间】:2012-04-29 07:39:18
【问题描述】:

我有一个double 类型的共享变量。这个变量将被两个线程访问。一个线程永远只会写入变量,而另一个线程永远只会读取变量。

我这里还有比赛条件吗?如果是,在 C++ 中是否有一种“简单”的方式来实现原子访问?如果读取比写入多得多,我如何有效地实现它?我需要将变量标记为volatile吗?

编辑:好的,“阅读器”线程定期处理批量数据,并且新值的传播不是时间敏感的。与其实现我没有好方法测试的复杂联锁,我可以只声明编写器线程将写入的另一个临时变量。然后,当阅读器完成一批时,它可以将临时值自动传播到实际变量。那会是无竞争条件的吗?

【问题讨论】:

  • @Jesse:许多编译器还没有该头文件的有效实现。
  • 一个简单的互斥锁可以在这里工作,以确保您正在阅读良好的价值。当写入线程正在使用该变量时,互斥锁将阻止对该变量的访问,并且当它完成时将其解锁以供读取线程完成他的工作。在 unix 和 windows 上,实现都非常简单。
  • @Chris911:互斥量不必要地昂贵并且可能阻塞一个线程。有可能实现免等待。
  • @Jakub:您的编辑不正确。读者仍然有可能在作者更改副本的同时制作副本,并看到部分更新的值。
  • 你的架构是什么?答案在很大程度上取决于您要使用的硬件。

标签: c++ multithreading atomic


【解决方案1】:

是的,存在竞争条件,因为double 变量在大多数处理器上都不是原子的。

使用 3 个双精度数(可能是一个中间有额外填充的数组,以避免错误共享导致性能下降)。

一个归读者所有,一个归作者所有,一个正在移交。

写入:写入写入槽,然后以原子方式交换(例如使用InterlockedExchange)写入槽的指针/索引与切换槽的索引。由于索引是指针大小或更小,只要变量正确对齐,原子交换就很容易。如果您的平台偶然提供了带或不带内存屏障的互锁交换,请使用带有或不带内存屏障的平台。

读取:以原子方式将读取槽的指针/索引与切换变量的索引交换。然后读取读槽。

您实际上还应该包含一个版本号,因为读取线程往往会在最新和上一个插槽之间反弹。阅读时,交换前和交换后都阅读,然后使用更高版本的。

或者,在 C++11 中,只需使用 std::atomic

警告:以上仅适用于单一作者/单一读者(本问题中的特殊情况)。如果您有多个,请考虑使用读写器锁或类似方式保护对变量的所有访问。

【讨论】:

  • 这会如何影响性能?我说的是每秒发生数百次读取,而写入可能每小时发生一次,即使有。
  • @Jakub:每秒数百次不算什么,你甚至不用担心性能。如果是每秒几十万次,那将是值得关注的时候了,我认为我的建议仍然是最快的。
  • 为什么不直接使用 64 位联合?一个字段是 64 位 int,另一个字段是 double。您可以整天对 64 位 int 进行 CAS 处理。您可以使用 64 位 int 复制并集,并在复制完成后读取双精度。
【解决方案2】:

您可能想看看讨论原始类型的读/写的这篇文章:

Are C++ Reads and Writes of an int Atomic?

【讨论】:

  • 这对double 有什么帮助,它的大小通常与int 不同,而且对齐要求也不同?
  • 我认为我想从那个线程中得到的关键点是读写真的取决于你使用的系统的底层架构(我相信一些多字节 x64 指令是原子如果正确对齐),编译器(即msdn.microsoft.com/en-us/library/aa290049%28VS.71%29.aspx)等。正如你所指出的,假设它不是原子的并从那里开始会更安全。我本可以在回复中更清楚地说明这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-21
  • 2023-03-23
  • 2018-11-08
  • 1970-01-01
  • 1970-01-01
  • 2022-01-18
  • 1970-01-01
相关资源
最近更新 更多