【问题标题】:c++11 atomic decrement of uint64_t cycles at 0c++11 uint64_t 周期原子减量为 0
【发布时间】:2015-03-15 18:46:01
【问题描述】:

我正在尝试编写一个简单的并行处理系统,它应该生成 N 个对象以放入多线程环境中的容器中。

为了告诉线程何时停止生成对象,我创建了一个简单的反向计数器,它从 N 开始,在 0 处运行,每个线程并行递减它。 计数器应该是 uint64_t,我想尝试 C++11 的原子支持。代码是这样的

//Class member
std::atomic<uint_fast64_t> counter

//Parallel function
while(counter-- > 0)
{
do something
}

它正确编译并执行,但它进入了一个无限循环,因为一旦计数器达到 0,它会进一步递减,但它会跳回可用的最高整数,因此永远不会停止。

将类型更改为 int64 而不是 uint64 可以解决问题,但我想了解为什么我需要这种解决方法。

我目前的工作假设是,即使条件为假,减量仍然会完成,所以当第一个线程检查计数器为 0 时,它无论如何都会减量,并且操作减法操作并不真正关心编码整数,但执行一个简单的按位运算(我忘记了到底是哪个,但我记得加法和减法是通过简单的按位异或和移位完成的),在下一次迭代中被解释为最大 uint 值。你觉得这个解释合理吗?

除了从 uint 切换到 int 之外,还有一个选择是将操作从减量切换到增量,但是您能想出一种不同的算法来在这种情况下保持减量吗?

编辑1

我想到的另一个可能的解决方案,虽然不是特别优雅,但知道实际并行启动了多少线程,将计数器停止在 N_Threads 处,起始值为 Tot+NThreads

//In calling function
counter = Tot+NThreads

//Parallel function
while(counter-- > NThreads)
{
do something
}

【问题讨论】:

  • 当然可以,你告诉它的。如果计数器为零,它会递减并退出。所有其他线程现在都在疯狂地减少现在非常高的计数器,没有希望取得重大进展。
  • 为什么不在while循环内做减量?
  • @Moberg:考虑两个线程的情况。因为counter == 1,两者都进入了while循环。然后,一旦两者都在循环中,它们都会递减counter。现在counter == -1
  • 正如比尔·林奇所说,如果你在两个不同的地方进行变量评估和递减,你不能保证在此期间没有其他人在做同样的事情
  • while(counter-- &gt; NThreads) 的性能并不比while(counter++ &lt; NThreads) 高,而且惯用语也明显低于while(counter++ &lt; NThreads)

标签: c++ multithreading c++11 atomic decrement


【解决方案1】:

原子性只保证所有线程都能看到atomic 值的一致值。通常像-- 这样的操作是读-修改-写操作。 atomic 只保证没有其他线程修改计数器,而另一个线程正忙于修改它。

澄清:atomic 防止数据竞争,仅此而已。

假设两个线程,T1 和 T2 以及以下 R、M、W 序列:现在线程 T2 的结果已被 T1 的结果覆盖,即计数器的值没有一致

T1:读取 T2:读取 T2:修改 T1:修改 T2:写入 T1:写入

因此,在您的问题中,代码执行counter--,这意味着--始终完成,无论其值是多少。因此,如果该值已经为零,那么现在将是 -1,或者,当使用 unsigned 数据类型时,是 unsigned 类型的最大值。

【讨论】:

  • operator-- std::atomic 应该是原子的,不是吗?无论如何,我正在阅读的参考资料是这样说的。但这无济于事,遇到完全相同的问题。
  • 但是atomic 不是您需要的保证。您需要“仅在 > 0 时递减”。 just atomic 保证你不能这样做。正如解释的那样。
  • 是的,我现在明白了,我读错了你的答案,并认为你是说时间线是问题所在,而不是没有发生的事情..
  • 但是这个保证其实已经足够了,对吧?我的意思是,你可以做counter-- &lt; very_large_value(值足够小,以至于线程过度递减计数器无法达到)
  • 不,因为减量是无条件地完成的。仅在减量后检查该值并做出线程对do something 的决定。
【解决方案2】:

你的假设

这或多或少是正确的。 (unsigned) 0 - 1 将是最大的无符号整数。即使条件为假,您的原子减量也将始终发生。

我们如何解决这个问题?

我相信您实际上正在寻找这样的东西:

std::atomic<uint_fast64_t> counter;
while (true) {
    uint_fast64_t cur = counter;
    if (cur == 0)
        break;
    if (counter.compare_exchange_strong(cur, cur - 1) == false)
        continue;

    ... // Perform work
} 

首先,我们测试计数器的当前值是否为 0。如果是,我们就完成了工作,我们应该退出。

如果它大于 0,那么我们需要减少计数器。这是一个比较和交换操作。因此,如果值在到达第二个原子操作所需的时间内没有改变,我们执行递减,然后做一些工作。如果我们被抢占了,那我们就再试一次。

【讨论】:

  • 是的,这实际上很有趣。我没有注意到比较功能。我刚开始使用原子头。谢谢。如果我有足够的声誉,我会投票赞成:)
  • @Triskeldeian:如果您不相信这是有效的,这里是(或多或少)Linux 内核中的等效代码:lxr.free-electrons.com/source/include/linux/atomic.h#L97
猜你喜欢
  • 1970-01-01
  • 2017-05-09
  • 1970-01-01
  • 1970-01-01
  • 2013-03-21
  • 2016-12-27
  • 1970-01-01
  • 2016-11-10
  • 2016-06-10
相关资源
最近更新 更多