C++原子变量atomic详解

b站视频
文章1

C++中原子变量确保共享变量的操作在执行时不会被其他线程的操作干扰。
无法复制/移动对象。

memory_order

atomic对象可以通过指定不同的memory orders来控制其对其他非原子对象的访问顺序和可见性,从而实现线程安全。常用的memory orders包括(参考:链接):

memory_order_relaxed

memory_order_relaxed: 无序的内存访问,允许编译器做更多的优化。

#include<bits/stdc++.h>
#include<thread>
using namespace std;

atomic<bool> x,y;
atomic<int> z;
void write_x_then_y()
{
    x.store(true, memory_order_relaxed);
    y.store(true, memory_order_relaxed);
}
void read_y_then_x()
{
    while(!y.load(memory_order_relaxed));  // y为true时会退出while
    if (x.load(memory_order_relaxed))    // x也为true时,z的值会改变
        ++z;
}

int main() {
    x=false;
    y=false;
    z=0;
    thread a(write_x_then_y);
    thread b(read_y_then_x);
    a.join();
    b.join();
    cout <<  noboolalpha << (z.load()!=0) << endl;
}

仅保证该原子类型变量的操作是原子化的,write_x_then_y()中对x和y的赋值是无序的,也就是说可能出现先给y赋值然后给x赋值,也可能出现先给x赋值然后给y赋值。所以read_y_then_x()中可能出现y为true,但x不为true的情况。(我的理解)
对上面代码进行实际测试,并没有出现y为true但x不为true的情况。这不能说明问题,因为其他编译器的优化策略,可能会导致出现y为true但x不为true的情况。

memory_order_release与memory_order_acquire

memory_order_release:

#include<bits/stdc++.h>
#include<thread>
using namespace std;

atomic<bool> x,y;
atomic<int> z;
// void write_x_then_y()
// {
//     x.store(true, memory_order_relaxed);
//     y.store(true, memory_order_release);
// }
// 下面函数和上面函数等价
void write_x_then_y()
{
    x.store(true, memory_order_relaxed);
    atomic_thread_fence(memory_order_release);
    y.store(true, memory_order_relaxed);
}

void read_y_then_x()
{
    while(!y.load(memory_order_acquire));  // y为true时会退出while
    if (x.load(memory_order_relaxed))    // x也为true时,z的值会改变
        ++z;
}

int main() {
    x=false;
    y=false;
    z=0;
    thread a(write_x_then_y);
    thread b(read_y_then_x);
    a.join();
    b.join();
    cout <<  noboolalpha << (z.load()!=0) << endl;
}

y.store(true, memory_order_release);保证了此语句前的语句写入完成。y.load(memory_order_acquire)用来“接住”使用memory_order_release写入的y。由于y.store(true, memory_order_release);保证了此语句前的语句写入完成,所以y.load(memory_order_acquire)后面的语句读取到的x都写入完成的x。(我的理解)

memory_order_seq_cst

memory_order_seq_cst是原子操作的默认参数,代码的顺序是怎么样的,底层的内存访问顺序就是怎么样的。(我的理解)

memory_order_consume

编译器一般都不怎么支持memory_order_consume,它保证本次读取之前所有依赖于该原子类型变量值的操作都已完成,但不保证其他线程对该变量的存储结果已经可见。(看不太懂)

atomic_flag

std::atomic_flag 是 C++ 中的一个原子布尔类型,它用于实现原子锁操作。
test_and_set() 函数会将标志位置为 true,并返回之前的值;clear() 函数将标志位置为 false。

#include <iostream>
#include <atomic>
#include <thread>

std::atomic_flag flag = ATOMIC_FLAG_INIT;

void func(int id) {
    while (flag.test_and_set(std::memory_order_acquire)) {
        // 等待其他线程释放锁
    }
    
    std::cout << "Thread " << id << " acquired the lock." << std::endl;
    
    // 模拟业务处理
    std::this_thread::sleep_for(std::chrono::seconds(1));
    
    flag.clear(std::memory_order_release);  // 释放锁
    std::cout << "Thread " << id << " released the lock." << std::endl;
}

int main() {
    std::thread t1(func, 1);
    std::thread t2(func, 2);
    
    t1.join();
    t2.join();
    
    return 0;
}

atomic_flag与mutex:atomic_flag使用test_and_set和clear函数来设置和清除标志位,以实现互斥。这种方式比较底层,需要手动编写循环来实现自旋等待。而mutex则提供了更高级的接口,使用lock和unlock函数来加锁和解锁,在需要等待资源时可以使用std::condition_variable等待条件的满足。

C++11自旋锁的实现:

#include <mutex>  
#include <atomic>  
  
class spin_lock {  
private:  
    std::atomic_flag flag = { ATOMIC_FLAG_INIT };  
  
public:  
    spin_lock() {}  
  
    void lock() {  
        while (flag.test_and_set(std::memory_order_acquire)) {  
            // spin-wait loop  
        }  
    }  
  
    void unlock() {  
        flag.clear(std::memory_order_release);  
    }  
};

自旋锁避免了操作系统进程调度和线程切换,如果已知临界区代码执行时间短,那么可以使用自旋锁,而不是互斥锁。由于自旋锁是循环等待,所以等太久,就很浪费cpu

自定义类型作为原子类型(没看懂,有空再说)

atomic:T可以是自定类型,不过有以下注意点:

必须使用编译器生成的复制构造函数?
不可以有虚函数或继承虚类
operator=等价与operator& ?

posted @ 2023-09-27 21:36  好人~  阅读(462)  评论(0编辑  收藏  举报