目录
(2)赋值(store)、读取(load)与交换(exchange)
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宏初始化。
#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,进行访问,之后清除。
#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用来交换新值,返回旧值。
#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等。
原子算术运算后,都会返回原值。
#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实现比较及交换功能,二者的功能是一样的,但是前者性能更好一些,常在高速循环中使用。
参数传入期待值与新值,通过比较当前值与期待值的情况进行区别改变。
#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