std::atomic
文章目录
C++中原子变量(atomic)是一种多线程编程中常用的同步机制,它能够确保对共享变量的操作在执行时不会被其他线程的操作干扰,从而避免竞态条件(race condition)和死锁(deadlock)等问题。
C++11起提供了atomic,可以使用它定义一个原子类型。
template< class T >
struct atomic;
构造函数
std::atomic::atomic
std::atomic<bool> ready (false);
std::atomic<int> a;
is_lock_free函数
is_lock_free函数是一个成员函数,用于检查当前atomic对象是否支持无锁操作。调用此成员函数不会启动任何数据竞争
bool is_lock_free() const volatile noexcept;
bool is_lock_free() const noexcept;
std::atomic_flag
std::atomic_flag是 C++ 中的一个原子布尔类型,它用于实现原子锁操作。
- std::atomic_flag 默认是清除状态(false)。可以使用 ATOMIC_FLAG_INIT 宏进行初始化,例如:std::atomic_flag flag = ATOMIC_FLAG_INIT;
- std::atomic_flag 提供了两个成员函数 test_and_set() 和 clear() 来测试和设置标志位。test_and_set() 函数会将标志位置为 true,并返回之前的值;clear() 函数将标志位置为 false。
- std::atomic_flag 的 test_and_set() 和 clear() 操作是原子的,可以保证在多线程环境下正确执行。
- std::atomic_flag 只能表示两种状态,即 true 或 false,不能做其他比较操作。通常情况下,std::atomic_flag 被用作简单的互斥锁,而不是用来存储信息。
atomic_flag实现原子锁
//原子锁
class CASLock : Noncopyable {
public:
typedef ScopedLockImpl<CASLock> Lock;
CASLock(){
m_mutex.clear();
}
~CASLock(){
}
void lock(){
while(std::atomic_flag_test_and_set_explicit(&m_mutex , std::memory_order_acquire));
}
void unlick(){
std::atomic_flag_clear_explicit(&m_mutex , std::memory_order_release);
}
private:
// 原子状态
volatile std::atomic_flag m_mutex;
};
atomic_flag实现自旋锁
atomic_flag lock = ATOMIC_FLAG_INIT;// 设置为false状态
void f(int n){
// test_and_set会不断设置新值并返回旧值
while (lock.test_and_set(std::memory_order_acquire))
cout << "Waiting from thread " << n << endl;
cout << "Thread" << n << " starts working" << endl;
}
void g(int n) {
cout << "Thread " << n << " is going to start" << endl;
lock.clear();// 将lock设置false
cout << "Thread " << n << " starts working" << endl;
}
void test() {
lock.test_and_set();
thread t1(f, 1);
thread t2(g, 2);
t1.join();
sleep(5);
t2.join();
}
// 当t1执行后,lock为true,则一直自旋等待,直到t2加入后,clear将其设置为false,t1才终止自旋,执行后续代码;
store函数
void store(T desired, std::memory_order order = std::memory_order_seq_cst) volatile noexcept;
void store(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;
desired:要存储的值。
order:存储操作的内存顺序。默认是std::memory_order_seq_cst
#include <iostream>
#include <atomic>
int main()
{
std::atomic<int> atomic_int(0);
int val = 10;
atomic_int.store(val);
std::cout << "Value stored in atomic object: " << atomic_int << std::endl;
return 0;
}
//Value stored in atomic object: 10
内存模型
【硬件内存模型】
当执行按照一定的顺序执行,则该内存模型为强顺序;
当执行不一定按照顺序执行,则该模型为弱顺序;
在多线程中,当线程间的内存数据被改变的顺序与机器指令中声明的不一致,则为弱顺序;
而上述的原子操作时强顺序的;
- 弱顺序的指令执行性能一般较高,当没有要求需要顺序执行时,使用该方式可以给程序提高性能;
【C++内存模型】
- 编译器保证原子操作间的指令顺序不变;
- 处理器对原子操作的汇编指令的执行顺序不变;
部分平台会阻止编译器优化,加入内存栅栏来保证atomic的顺序一致性,将会大大影响性能;
【如何解决不阻止编译器优化】:让程序为原子操作指定内存顺序(memory_order);
能够让编译器重排序或处理器乱序执行;
memory_order_relaxed :不对执行顺序做任何保证;
memory_order_acquire :本线程中,所有后续的读操作必须再本条原子操作完成后执行;
memory_order_release :本线程中,所有之前的写操作完成后才能执行本条原子操作;
memory_order_acq_rel :同时包含memory_order_acquire、memory_order_release;
memory_order_consume :本线程中,所有后续的有关原子类型操作,后再本条原子操作完成后执行;
memory_order_seq_cst :全部存取都按顺序执行(可能会导致性能损失);
- 上述中,可以使用memory_order_release和memory_order_consume产生;
load函数
load函数用于获取原子变量的当前值。
T load(memory_order order = memory_order_seq_cst) const noexcept;
operator T() const noexcept;
load函数的参数memory_order表示内存序,也就是对原子变量的读操作要遵循哪种内存模型。C++中定义了多种内存序,包括:
- memory_order_relaxed:最轻量级的内存序,不提供任何同步机制。
- memory_order_acquire:在本线程中,所有后面的读写操作必须在这个操作之后执行。
- memory_order_release:在本线程中,该操作之前的所有读写操作必须在这个操作之前执行。
- memory_order_seq_cst:最严格的内存序,保证所有线程看到的读写操作的顺序都是一致的。
使用load函数时,如果不指定memory_order,则默认为memory_order_seq_cst。
std::atomic<int> foo (0);
int x;
do {
x = foo.load(std::memory_order_relaxed); // get value atomically
} while (x==0);
exchange函数
访问和修改包含的值,将包含的值替换并返回它前面的值。
template< class T >
T exchange( volatile std::atomic<T>* obj, T desired );
示例
// atomic::load/store example
#include <iostream> // std::cout
#include <atomic> // std::atomic, std::memory_order_relaxed
#include <thread> // std::thread
//std::atomic<int> count = 0;//错误初始化
std::atomic<int> count(0); // 准确初始化
void set_count(int x)
{
std::cout << "set_count:" << x << std::endl;
count.store(x, std::memory_order_relaxed); // set value atomically
}
void print_count()
{
int x;
do {
x = count.load(std::memory_order_relaxed); // get value atomically
} while (x==0);
std::cout << "count: " << x << '\n';
}
int main ()
{
std::thread t1 (print_count);
std::thread t2 (set_count, 10);
t1.join();
t2.join();
std::cout << "main finish\n";
return 0;
}