原子操作
原子操作是被保证以单独一个事务被执行的操作,其他线程可以看到执行前的系统状态、或者看到原子操作执行结束后的系统状态,但是看不到原子执行中的系统执行状态;
由于总线资源的独占性,一次读或者一次写是天然的原子操作;其他情况下CPU可以通过锁总线和锁cache开始先原子操作
CAS是compare and set,是一条cpu原子指令,一个主要用途是用CAS实现自旋锁
class Spinlock{
int i=0;
void lock(){
while(!cas(&i,0,1)); // 相当于i++
}
void release(){
while(!cas(&i,1,0)); // 相当于i--
}
};
bool compare_and_swap(&old_value,expect,new_value);
C++的原子操作
C++11标准模板库中提供了atomic的模板开定义原子类型
- load() 读操作
- store()写操作
- compare_exchange_weak/compare_exchange_strong CAS操作
对于整形还提供一些特殊的成员函数
- fetch_add() 原子加
- fetch_sub() 原子减
- fetch_and() 原子与
- fetch_or() 原子或
std::atomic_flag,最简单的atomic类型,该对象可以在两个状态之间切换,设置和清除;对象必须被ATOMIC_FLAG_INIT初始化;std::atomic_flag flag = ATOMIC_FLAG_INIT;初始化的对象只能做三件事:销毁,清除、设置;
对应成员函数:
- clear()
- test_and_set()
非常适合用在自旋锁中
class spinlock_mutex
{
std::atomic_flag flag;
public:
spinlock_mutex():
flag(ATOMIC_FLAG_INIT)
{}
void lock()
{
while(flag.test_and_set(std::memory_order_acquire));
}
void unlock()
{
flag.clear(std::memory_order_release);
}
};
atomic<>并不能保证是无锁的,会根据硬件设备来选择是无锁实现还是有锁实现:
-
atomoc_flag可以保证在任何机器硬件上都是无锁的;
-
atomic中,T的大小是1、2、4、8个字节时,基本可以保证是无锁实现的
-
对于自建类型,若是TriviallyCopyable类型,且是”位可比的“ 才能通过无锁实现;
riviallyCopyable类型等价于memcpy(),自建类型的拷贝、赋值函数是编译器自动生产的,保证CAS操作正常
对于atomic类型,STL提供了id_lock_free(),判断在目前机器中该类型是无锁还是有锁的;
原子类型的CAS函数
- compare_exchange_weak(expect, desired);
- compare_exchange_strong(expect, desired);
c++原子操作的六种内存顺序
现在CPU采用过发射技术和流水线技术,为了避免流水线中断,CPU会对指令进行适当的重排
atomic原子操作可以使用memory_order来控制变量在不同线程的可见顺序的可见性,有以下六种内存顺序可选择
typedef enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
} memory_order;
-
memory_order_release/memory_order_acquire
memory_order_release是写操作store函数,表示该操作之前的任何写操作都不能放到该操作之后,即写语句不能调到本语句之后;
memory_order_acquire是读操作load函数,表示该读操作之后的的任何读内存操作都不放到该操作之前
对于同一个原子变量,release操作之前的写操作,一定对随后的acquire操作后的读可见,这两种内存序一般需要配对使用
-
memory_order_release/memory_order_consume
此搭配同上一个基本一样,带式该组合是一种更为宽松的内存序可见情况,comsume只是阻止对该院系变量有依赖的操作重排到前面去,而非所有读操作
-
memory_order_acq_rel
此内存序是release和acquire的结合,包含这两种的特性,因此任何读写操作的重排都不能跨越这个条用
-
memory_order_seq_cst
默认内存序选项,是最严格的内存顺序,前面的语句不能挑到后面,后面的语句不能调到前面
-
memory_order_relaxed
只保证当前语句是原子操作,对你内存顺序不做任何保证
C++多线程编程:原子类型与内存顺序 - 知乎 (zhihu.com)
专属学习链接:https://xxetb.xetslk.com/s/36yiy3