首页 > 解决方案 > Different ways of implementing atomic *=

问题描述

Studying through a book, it explains how to implement more complex operations like operator* for std::atomic<T>. Implementation uses compare_exchange_weak and I think I understood how this works. Now, I implemented things myself, take a look.

#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";
} 

I have two versions of the code, book's version is commented out. I don't get why I should wait on a busy-loop to perform atomic compare_exchange. In my version, I've just called it on its own line and looking at the generated assembly in Godbolt, both uses

lock cmpxchg dword ptr [rsp + 8], ecx

and looks pretty similar to me. So, why should I need a wait-loop like the one in the book to make this thing atomic? Isn't my version also fine and do work atomically?

标签: c++atomic

解决方案


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

compare_exchange_weak工作方式如下:

原子地比较 *this 的(对象表示(C++20 前)/值表示(C++20 起))与期望的,如果它们按位相等,则将前者替换为期望(执行读取修改-写操作)。否则,将存储在 *this 中的实际值加载到预期中(执行加载操作)。 参考

基于上面的描述t1不会被改变,你的乘法也不会被存储。通过循环,您可以确保更新t1并存储乘法的结果,或者更新expected并在循环的下一次迭代中重试(循环只会在第一种情况发生时停止)。


编辑:您可以通过模拟并发访问来“尝试”它。在交换结果之前,另一个线程进来并更改原子的值。以下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";
} 

推荐阅读