【问题标题】:Don't really get the logic of std::atomic::compare_exchange_weak and compare_exchange_strong不要真正理解 std::atomic::compare_exchange_weak 和 compare_exchange_strong 的逻辑
【发布时间】:2023-03-27 11:42:01
【问题描述】:

我读过https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange

以原子方式比较对象表示(C++20 前)的值 *this 与期望的表示(C++20 起),如果 那些是按位相等的,用期望的替换前者(执行 读-修改-写操作)。否则,加载存储的实际值 在 *this 中进入预期(执行加载操作)。

所以我理解的代码就像

bool expected=true;
extern atomic<bool> b = false; 
void foo ()
{
//
    while(!b.compare_exchange_weak(expected, false));
//
}

在循环运行一次之后(忽略虚假失败),它将失败,并将写入预期的false,因此在第二次迭代中 compare_exchange_weak 将返回成功,尽管 b 尚未更改为 true。但这一切的意义何在?我虽然可以用它作为同步锁,等待其他线程更改b,但现在想不出这个的用法。

来自 cppreference 的示例还表明,在两次调用之后 compare_exchange_strong 将成功。

#include <atomic>
#include <iostream>
 
std::atomic<int>  ai;
 
int  tst_val= 4;
int  new_val= 5;
bool exchanged= false;
 
void valsout()
{
    std::cout << "ai= " << ai
          << "  tst_val= " << tst_val
          << "  new_val= " << new_val
          << "  exchanged= " << std::boolalpha << exchanged
          << "\n";
}
 
int main()
{
    ai= 3;
    valsout();
 
    // tst_val != ai   ==>  tst_val is modified
    exchanged= ai.compare_exchange_strong( tst_val, new_val );
    valsout();
 
    // tst_val == ai   ==>  ai is modified
    exchanged= ai.compare_exchange_strong( tst_val, new_val );
    valsout();
}

结果:

ai= 3  tst_val= 4  new_val= 5  exchanged= false
ai= 3  tst_val= 3  new_val= 5  exchanged= false
ai= 5  tst_val= 3  new_val= 5  exchanged= true

【问题讨论】:

  • compare_exchange 为您提供同步交易风格。如果交易失败,重新计算结果并再次尝试提交。
  • @rustyx 不,没有解释 std::atomic::compare_exchange 应该在哪里和什么地方使用
  • “我真的不明白 [it] ...这一切有什么意义?” 问题可能过于宽泛,无法获得您正在寻找的特定答案为了。你要的是真实世界的例子吗?
  • @DrewDormann 是的,我现在不太明白他的用途和意义是什么

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


【解决方案1】:

我将举一个我确实使用过的例子,因为它非常简单。

我有 atomic,它描述了某物的可用大小。 有整数溢出的危险,所以我在减去一个值之前先做检查。

不完全复制粘贴我的生产代码:

class LockFreeCuncuretSizeLimit {
public:
    explicit LockFreeCuncuretSizeLimit(size_t available) : mSize{available}
    {}

    bool tryAcuqire(size_t s) {
        size_t lastSize = mSize;
        size_t newSize;
        do
        {
            if (lastSize >= s)
            {
                newSize = lastSize - s;
            }
            else
            {
                return false;
            }

        }
        while (!mSize.compare_exchange_weak(lastSize, newSize));
        return true;
    }

    void release(size_t s) {
        mSize += s; // here I do not have worry about integer overflow
    }
private:
    std::atomic<size_t> mSize;
};

现在尝试在没有compare_exchange_strong 且没有竞争条件的情况下执行图像操作。

有一个条件满足的变化,但是当在原子上进行减法时,其他一些线程已经减去了一个值,所以当我进行实际减法时,可能会溢出整数。所以这不能没有compare_exchange_strong

现在compare_exchange_strongcompare_exchange_weak 之间的区别很难解释。甚至 Herb Sutter 在一些 cppcon 谈话中也放弃了解释,并提供了简单的规则:“如果你需要循环使用compare_exchange_weak,否则使用compare_exchange_strong”。

【讨论】:

  • 所以想象其他线程在当前线程lastSize被初始化后改变了mSize。当我们到达 while (!mSize.compare_exchange_strong(lastSize, newSize));它将失败,lastSize 将使用新的更新,从 mSize 的其他线程值更改,并且在下一次迭代中将 mSize 分配给 newSize。我还是不明白这是什么意思:(
  • 这是事务同步。就像在数据库中一样,但在小对象上。 compare_exchange_strong 尝试提交事务。如果由于输入值更改而提交失败,则此代码再次循环,重新计算新结果,然后尝试提交更新的值。
  • 试着想象两个线程并行运行相同的代码行。在达到mSize.compare_exchange_strong 之前,所有值都将相同。在这个地方,这两个线程将分叉他们的行为,因为compare_exchange_strong 将只为一个线程返回true
  • 所以对我来说,最终这个线程的 newSize 之一将覆盖另一个
  • 真的被你的最后一行搞糊涂了。当然,在循环中是使用弱的时候了。在循环之外,您必须使用strong。你能把我链接到 Herb 演讲吗?!
【解决方案2】:

std::atomic::compare_exchange_weak 以线程感知的方式执行此英语任务:

因为变量持有expected,它现在应该持有desired

作为一项微不足道的任务,假设您的std::atomic&lt;int&gt; x 的值应该是平方。但是其他线程可能正在修改它,因此您不能简单地读取该值,将其平方并写回新值。它可能已经改变了!

这是执行此任务的线程安全方式。您保证存储的值将被其平方替换。

int expected = x;
while( !x.compare_exchange_weak(expected, expected*expected) );

此代码将自动将expected 替换为其正方形除非值已更改。

如果值已更改,expected 现在会使用新值更新,代码会再次尝试。

【讨论】:

  • 谢谢你的好榜样!我希望我可以将两个答案标记为已接受:(
猜你喜欢
  • 2014-10-01
  • 2011-06-24
  • 2021-06-08
  • 1970-01-01
  • 1970-01-01
  • 2022-01-23
  • 2020-08-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多