【问题标题】:How to multiply two values and store the result atomically?如何将两个值相乘并以原子方式存储结果?
【发布时间】:2019-07-07 10:46:24
【问题描述】:

假设我的代码中有以下全局变量:

std::atomic<uint32_t> x(...);
std::atomic<uint32_t> y(...);
std::atomic<uint32_t> z(...);

我的任务是将 x 和 y 相乘,然后将结果存储在 z 中:

z = x * y

我知道在每个对象上调用 store() 和 load() 的天真方法是完全错误的:

z.store(x.load() * y.load()); // wrong

这样我正在执行三个单独的原子指令:另一个线程可能会滑过并同时更改其中一个值。

我可以选择比较和交换 (CAS) 循环,但它只能保证原子性,仅用于将 z 的旧值与新值 (x*y) 交换:我仍然不确定如何在单个原子步骤中执行整个操作。

我也知道将 xyz 包装在一个结构中并使其成为原子在这里是不可行的,因为该结构不适合单个 64 位寄存器。编译器会在引擎盖下使用锁(如果我在这里错了,请纠正我)。

这个问题只能用互斥锁解决吗?

【问题讨论】:

  • 它不能在汇编级别完成,所以在没有互斥锁的 C++ 级别就不行。
  • 也许这是一个更有成果的问题:为什么需要第三个原子变量,它只是对其他两个变量的计算结果?关键是,每当您需要 x 和 y 的乘积时,您可以原子地读取这两个变量并计算它,除非还有其他一些您没有编写的魔法正在发生。
  • @UlrichEckhardt z,保存结果的变量必须是原子的,因为它是由其他线程写入/读取的。
  • 是的。但是它们是 64 位的,所以应该是可行的,对吧?
  • @Ignorant 对于具有更多术语的操作,解决方案是事务性内存。操作是数据库意义上的“事务”,提供 ACID 保证(在大多数情况下)。事务尝试要么提交(在这种情况下,所有操作似乎都是原子的)要么失败(在这种情况下,更改被回滚以保持一致的状态)并且必须重试(如 CAS 循环)。 TM 是一个开放的研究课题。硬件 TM 支持才刚刚成为现实。软件 TM 已经存在了一段时间。 TBH 我不知道它们是如何工作的,但我猜它们使用了很多 CAS?

标签: c++ multithreading concurrency atomic stdatomic


【解决方案1】:

我仍然不确定如何在单个原子步骤中执行整个操作。

只有在您的架构支持“32 位原子乘法”之类的东西(并且您必须在 C++ 标准的设施之外进行)或足够宽以执行 RMW 操作的原子时,才有可能这样做在 64 位上。

我也知道将 xyz 包装在一个结构中并使其成为原子在这里是不可行的,因为该结构不适合单个 64 位寄存器。

即使它们适合,您仍然需要执行 RMW 操作,因为无论如何您都不太可能进行原子乘法。

这个问题只能用互斥锁解决吗?

如果您的架构支持无锁 64 位原子(请查看 is_always_lock_free),您可以将 xy 放在一起并根据需要对其执行操作。

如果变量改为uint64_t,或者操作比x * y * w * k / j 更复杂,该怎么办?

假设您的架构没有 128 位无锁原子,那么您就无法以原子方式加载那么多数据。要么设计你的程序,让它不需要(完整的)操作是原子的,要么使用锁,要么寻找一种方法来避免共享状态。

请注意,即使您认为某些操作是原子操作,您也必须意识到,在 SMP 系统中,您无论如何都会对缓存层次结构施加压力。

【讨论】:

    猜你喜欢
    • 2022-01-01
    • 2023-01-28
    • 1970-01-01
    • 2014-09-05
    • 1970-01-01
    • 2012-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多