C++(26): 原子操作(std::atomic)

目录

[1. 简述](#1. 简述)

[2. 什么是原子操作](#2. 什么是原子操作)

[3. C++原子操作](#3. C++原子操作)

[4. std::atomic_flag](#4. std::atomic_flag)

[5. std::atomic](#5. std::atomic)

(1)操作

(2)赋值(store)、读取(load)与交换(exchange)

(3)算术运算

(4)CAS


1. 简述

开发过多线程、并发编程的小伙伴一定接触过mutex,通过对资源进行加锁和解锁,实现对方问和修改的互斥操作。mutex使用起来很方便,很强大,但也有局限性。频繁地加锁和解锁会造成较大的资源消耗,影像系统的性能。

与mutex相比,原子(atomic)操作相对灵活和简单。

注意,这种灵活性在一定程度上是做了某些妥协的。

2. 什么是原子操作

原子指的是一系列不可被CPU上下文交换的机器指令,这些指令组合在一起就形成了原子操作。

我们在日常使用的CPU或SOC基本都是多核的情况,当其中某个CPU核心开始运行原子操作时,会先暂停其它CPU内核对内存的操作,保证对资源的独占性,进而保证资源不会被其它CPU内核所干扰,这就是原子操作的通俗解释。

3. C++原子操作

C++提供了一个模板类型std::atomic<T>来助力实现原子操作,还提供了一些特化的原子类型,例如std::atomic_int、std::atomic_long等。

此外还提供了std::atomic_flag这一超简单的原子类型,简单到只有设置(set)和清除(clear)两种状态。

4. std::atomic_flag

std::atomic_flag可以说得上是最简单的原子类型了,他只有设置(set)和清除(clear)两种状态。std::atomic_flag不可拷贝和赋值,且必须使用ATOMIC_FLAG_INIT宏初始化。

cpp 复制代码
#include <atomic>

std::atomic_flag flag = ATOMIC_FLAG_INIT;

关于atomic_flag我们只需要掌握两个成员的使用就可以了,他们分别是test_and_set和clear。

test_and_set用于判断当前变量是否被设置过,如果没有被设置过,则进行设置,并返回false,反之则直接返回true。

clear用于清除设置的状态。

如下所示的例程演示了排他性的访问某些资源,创建10个线程,分别访问同一段资源。当test_and_set返回true时,说明有其他某个线程正在访问,因此等待,知道test_and_set返回false,进行访问,之后清除。

cpp 复制代码
#include <iostream>

#include <atomic>

#include <thread>

#include <vector>

#include <sstream>


std::atomic_flag atomic_state = ATOMIC_FLAG_INIT;

std::stringstream stream_info;

void access_stream(int x)

{

    while (atomic_state.test_and_set()); ///< 等待状态被清除

    stream_info << "thread" << x << "access stream" << '\n';

    atomic_state.clear(); ///< 清楚状态

}

int main()

{

    std::vector < std::thread > threads;

    for(int i = 1; i <= 10; ++i){

        threads.push_back(std::thread(access_stream, i));

    }

    for(auto & th:threads){

        th.join();

    }

    std::cout << stream_info.str() << std::endl;;

    return 0;

}

5. std::atomic<T>

std::atomic<T>作为一个模板,提供了通用的原子类型,也提供了比std::atomic_flag更为灵活和复杂的应用功能。

std::atomic_int等作为特化的原子类型,是特殊的std::atomic<T>,一般来讲std::atomic_int等价于std::atomic<int>,其他特化类型类似。

(1)操作

std::atomic提供了赋值、算术运算和比较交换等操作。

(2)赋值(store)、读取(load)与交换(exchange)

std::tomic提供了store和load接口,分别用来赋值和读取,也提供了exchange用来交换新值,返回旧值。

cpp 复制代码
#include <iostream>

#include <atomic>


int main(int argc, char* argv[])

{

    std::atomic<int> atomic_int(0);

    atomic_int.store(10); ///< 设置原子变量的值

    std::cout << "value: " << atomic_int.load() << std::endl;

    int value = atomic_int.load(); ///< 读取原子变量的值

    std::cout << "value: " << value << std::endl;

    int old_value = atomic_int.exchange(20); // 交换原子变量的值

    std::cout << "old_value: " << old_value << ", new_value: " << atomic_int.load() << std::endl;

    return 0;

}

(3)算术运算

std::atomic提供了原子加,原子减等接口,具体包含fetch_add、fetch_sub、fetch_and、fetch_or和fetch_xor等。

原子算术运算后,都会返回原值。

cpp 复制代码
#include <iostream>

#include <atomic>


int main(int argc, char* argv[])

{

    std::atomic<int>    atomic_int(0);

    int last_value;

    last_value = atomic_int.fetch_add(10); ///< 原子加操作 

    std::cout << "last_value: " << last_value << ", new_value: " << atomic_int.load() << std::endl;

    last_value = atomic_int.fetch_sub(5); ///< 原子减操作

    std::cout << "last_value: " << last_value << ", new_value: " << atomic_int.load() << std::endl;

    last_value = atomic_int.fetch_and(0b1100); ///< 原子与操作

    std::cout << "last_value: " << last_value << ", new_value: " << atomic_int.load() << std::endl;


    last_value = atomic_int.fetch_or(0b1010); ///< 原子或操作

    std::cout << "last_value: " << last_value << ", new_value: " << atomic_int.load() << std::endl;

    last_value = atomic_int.fetch_xor(0b1111); ///< 原子异或操作

    std::cout << "last_value: " << last_value << ", new_value: " << atomic_int.load() << std::endl;

    return 0;

}

(4)CAS

CAS,Compare and Swap,比较并交换。

std::atomic提供了compare_exchange_weak和compare_exchange_strong实现比较及交换功能,二者的功能是一样的,但是前者性能更好一些,常在高速循环中使用。

参数传入期待值与新值,通过比较当前值与期待值的情况进行区别改变。

a.compare_exchange_weak(b, c)其中a是当前值,b期望值,c新值

a==b时:函数返回真,并把c赋值给a

a!=b时:函数返回假,并把a复制给b

cpp 复制代码
#include <iostream>

#include <atomic>


int main(int argc, char* argv[])

{

    std::atomic<int> a;

    a.store(10);

    int b=10;

    int c=20;

    std::cout<<"a:"<<a<<std::endl;

    if(a.compare_exchange_weak(b, c)){ ///< a和b值相同,把c的值赋给a

        std::cout<<"a true:"<<a.load()<<std::endl;

    }

    std::cout<<"a:"<<a<<" b:"<<b<<" c:"<<c<<std::endl;

    return 0;

}

>> 运行结果

a: 20 b:10 c:20
相关推荐
我是谁??12 分钟前
C/C++使用AddressSanitizer检测内存错误
c语言·c++
发霉的闲鱼1 小时前
MFC 重写了listControl类(类名为A),并把双击事件的处理函数定义在A中,主窗口如何接收表格是否被双击
c++·mfc
小c君tt1 小时前
MFC中Excel的导入以及使用步骤
c++·excel·mfc
xiaoxiao涛1 小时前
协程6 --- HOOK
c++·协程
羊小猪~~3 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
脉牛杂德4 小时前
多项式加法——C语言
数据结构·c++·算法
legend_jz4 小时前
STL--哈希
c++·算法·哈希算法
CSUC4 小时前
【C++】父类参数有默认值时子类构造函数列表中可以省略该参数
c++
Vanranrr4 小时前
C++ QT
java·c++·qt
鸿儒5174 小时前
C++ lambda 匿名函数
开发语言·c++