我正在尝试编写一个简单的并行处理系统,该系统应生成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处检查计数器时它无论如何都会递减它并且操作减法操作并不真正关心编码整数,但执行一个简单的按位运算(我忘了哪一个,但我记得加法和减法是通过简单的按位xor和移位完成的),在下一次迭代中,它被解释为max uint值。你认为这种解释是否合理?
除了从uint切换到int之外,一个选项是将操作从递减切换到递增但是你能想到一个不同的算法来保持这个上下文中的递减吗?
EDIT1
我想到的另一个可能的解决方案,即使不是特别优雅的是,知道实际并行启动了多少线程,以N_Threads停止计数器,其起始值为Tot + NThreads
//In calling function
counter = Tot+NThreads
//Parallel function
while(counter-- > NThreads)
{
do something
}
答案 0 :(得分:2)
原子性只保证所有线程都能看到atomic
值的一致值。通常,像--
这样的操作是读 - 修改 - 写操作。 atomic
仅保证没有其他线程修改计数器,而另一个线程正在忙于修改它。
澄清:atomic
阻止数据竞争,没有别的。
假设两个线程T1和T2以及随后的R,M,W序列:现在线程T2的结果已被T1的结果覆盖,即没有一致的计数器值。
T1:读T2:读T2:修改T1:修改T2:写T1:写
因此,在您的问题中,代码会执行counter--
,这意味着--
将始终完成,无论其值是什么。因此,如果该值已经为零,则现在为-1,或者在使用unsigned
数据类型时,为unsigned
类型的最大值。
答案 1 :(得分: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,那么我们需要递减计数器。这就是比较和交换操作。因此,如果值没有改变到达第二个原子操作所需的时间,我们执行减量然后做一些工作。如果我们被抢先一步,那么我们就再试一次。