实现原子* =的不同方法

时间:2018-08-13 05:28:12

标签: c++ atomic

通读本书,它说明了如何为operator*实施更复杂的操作,例如std::atomic<T>。实现使用compare_exchange_weak,我想我了解它是如何工作的。现在,我自己实现了一些东西,看看。

#include <type_traits>
#include <atomic>
#include <iostream>

/*template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
std::atomic<T>& operator*=(std::atomic<T>& t1, T t2) {
    T expected = t1.load();
    while(!t1.compare_exchange_weak(expected, expected * t2))
    {}
    return t1;
}*/

template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
std::atomic<T>& operator*=(std::atomic<T>& t1, T t2) {
    T expected = t1.load();
    t1.compare_exchange_weak(expected, expected * t2);
    return t1;
}

int main() {
    std::atomic<int> t1 = 5;
    std::atomic<int> t2;
    t2 = (t1 *= 5).load();

    std::cout << "Atomic t1: " << t1 << "\n";
    std::cout << "Atomic t2: " << t2 << "\n";
} 

我有两个版本的代码,本书的版本已被注释掉。我不明白为什么我应该等待一个繁忙的循环来执行原子compare_exchange。在我的版本中,我只是单独调用它,然后查看Godbolt中生成的程序集,两者都使用

lock cmpxchg dword ptr [rsp + 8], ecx

,看起来和我非常相似。那么,为什么我需要一个像书中那样的等待循环来使这件事原子化?我的版本还可以正常运行吗?

1 个答案:

答案 0 :(得分:6)

想象一下在调用loadcompare_exchange_weak之间,该值被另一个线程更改了。 expected不再具有当前值。

compare_exchange_weak的工作方式如下:

  

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

根据以上t1的描述,将不会更改,并且不会存储您的乘法。 通过循环,您可以确保更新t1并存储乘法结果,或者更新expected并在循环的下一次迭代中重试(循环只会在第一种情况发生后才停止)


编辑: 您可以通过模拟并发访问来“尝试”它。在交换结果之前,会有另一个线程进入并更改atomic的值。在下文中,compare_exchange_weak仅影响expected

+----------- Thread 1 -----------+---------- Thread 2 ----------+
| ex = t1.load()                 |                              |
|                                | t1.store(42)                 |
| t1.cmp_xchg_w(ex, ex * t2)     |                              |

此代码模拟并发访问并使单个线程进入睡眠状态。

#include <type_traits>
#include <atomic>
#include <iostream>
#include <chrono>
#include <thread>

template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
std::atomic<T>& operator*=(std::atomic<T>& t1, T t2) {
    using namespace std::chrono_literals;
    T expected = t1.load();
    std::this_thread::sleep_for(400ms);
    t1.compare_exchange_weak(expected, expected * t2);
    return t1;
}

int main() {
    std::atomic<int> t1 = 5;
    std::atomic<int> t2;
    std::thread th1([&](){
        t2 = (t1 *= 5).load();
    });
    std::thread th2([&](){
        using namespace std::chrono_literals;
        std::this_thread::sleep_for(100ms);
        t1.store(8);
    });

    th1.join();
    th2.join();

    std::cout << "Atomic t1: " << t1 << "\n";
    std::cout << "Atomic t2: " << t2 << "\n";
}