C++11 并发6 原子操作
原子的概念
原子操作就是这样的一种『小到不可分割的』操作.
对于int sum;sum+=i
这样的操作,汇编也是好几条指令,多个线程都访问时,也可能不是原子的。
atomic
- 原子数据类型
atomic
头文件。可以像使用内置的数据类型那样使用原子数据类型
(c++保证这些操作是原子操作)。对应于内置的数据类型,原子数据类型都有一份对应的类型。
原子本质就是锁,不过颗粒度更小。实现原子可以使用 cpu 的专用原子指令,或者自己实现一个锁。
用<atomic>
特定化的类型都有一个is_lock_free
函数,可以查询某原子类型的操作是直接用的原子汇编指令(x.is_lock_free()返回 true),还是编译器和库内部用了一个锁(x.is_lock_free()返回 false)。
std::atomic_int sum += i;
原子类型是不能赋值和拷贝的构造的,只能用load,store
这样的操作,当然还有更多更复杂的操作,如:
- exchange()
读-写-改
,将新值改为 val,旧值返回。
T exchange (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
- compare_exchage_strong()
比较/交换
,比较当前值与 expected,若为 true,存储 val,false 存储 expected。
bool compare_exchange_strong (T& expected, T val,
memory_order success, memory_order failure) noexcept;
- compare_exchange_weak()
有可能 expected 等于当前值,但是返回 false.为某些 loop 而优化。
对于指针型的std::atomic<T*>
,还有基本操作如
- fetch_add()
- fetch_sub()
存储地址上做原子加法和减法,为+=, -=, ++和–提供简易的封装;
class Foo{};
Foo some_array[5];
std::atomic<Foo*> p(some_array);
Foo* x=p.fetch_add(2); // p加2,并返回原始值
- atomic_flag
另外还有一类atomic_flag
,非常适合做自旋锁,就是个布尔标志,没有is_lock_free
函数,与锁无关,即线程访问时不需要加锁。
- test_and_set() 如果 false 则 true,但是返回 flase ,如果 true 则 true
- clear()
class spinlock_mutex
{
std::atomic_flag flag;
public:
//atomic_flag的初始操作是必须的std::atomic_flag f = ATOMIC_FLAG_INIT;
spinlock_mutex():
flag(ATOMIC_FLAG_INIT)
{}
void lock()
{
//test_and_set并不会阻塞
//自旋锁,意味着锁是很快可以得到的,才会在短时间快速尝试。
//如果普通情况这样干巴巴的等,这个while耗不起。
while(flag.test_and_set(std::memory_order_acquire));
}
void unlock()
{
flag.clear(std::memory_order_release);
}
};
下面的使用方式,有么有觉得眼熟?防止互斥访问的,用的真的不少。
static bool flag = false;
if(flag== false) {flag = true; return false;} else {return true;}
内存顺序语义
顺序一致性
是指线程执行的顺序和我们程序员所写代码的顺序是一致的。不然编译器判断执行顺序不影响程序结果的化,可能采用reorder
的手段优化。
std::memory_order
这个 enum 定义了 6 种语义, 来对内存操作的行为进行约定,这些语义分别规定了不同的内存操作在其它线程中的可见性问题, 默认的是顺序一致性。
typedef enum memory_order
{
memory_order_relaxed, //不对执行顺序做任何保证,自由序列
memory_order_consume, //本线程中所有有关本原子类型的操作,必须等到本条原子操作完成之后进行,获取-释放序列
memory_order_acquire, //本线程中,后续的<strong>读操作</strong>必须在本条原子操作完成后进行,获取-释放序列
memory_order_release, // 本线程中,之前的<strong>写操作</strong>完成后才执行本条原子操作,获取-释放序列
memory_order_acq_rel, //memory_order_acquire和memory_order_release 效果的合并,获取-释放序列
memory_order_seq_cst //顺序一致,排序一致序列
} memory_order;
发现这部分异常艰深,先缓一缓。
atomic 使用限制总结
非成员函数操作 atomic
不同的原子类型中也有等价的非成员函数
存在,另外考虑了要与C语言
兼容,在 C 中只能使用指针,而不能使用引用。后缀_exlicit
可以指定内存顺序
- std::atomic_load()
- std::atomic_load_explicit()
- std::atomic_store(&atomic_var,new_value)
- std::atomic_is_lock_free()
- std::atomic_flag_test_and_set()
- std::atomic_flag_clear()
注意shared_ptr
特殊,一般原子类型才能使用上述函数,但它类似原子类型
。
std::shared_ptr<my_data> p;
void process_global_data()
{
std::shared_ptr<my_data> local=std::atomic_load(&p);
process_data(local);
}