CPP11中的atomic类以及memory_order

本文详细介绍了C++11中引入的原子类型(atomic),包括其基本概念、应用场景及核心函数compare_exchange_weak()与compare_exchange_strong()的区别。同时,深入探讨了memory_order在多线程编程中的作用,并给出基于atomic实现自旋锁的例子。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Atomic

c++11中新引入了atomic类型,即原子量,定义在头文件”atomic”中.
其定义的atomic operation,是一种无法再进行细分的操作,用于解决并发操作中数据竞争的问题.观察一个atomic变量时,观察者只会得到变量的原值或变量被他人修改后的新值,而不会观察到一个修改中的状态.

例如,现有一个变量值为二进制10,需要将其修改为01,写操作每次修改一位数字,需要进行两次操作(先把首位1修改成0,再将第二位0改成1),对于一个非atomic变量,观察者可能会看到00值(修改进行了一半),使用atomic则相当于对写操作进行了打包,保证观察者要么看到10,要么看到01,从而保证了值的完整性和操作结果的确定性.
(这里只是举个栗子,实际在x86/64的机器上,对于整型的操作本身就是原子的)


atomic中的 load(),store(),exchange() 都较易于理解,
这里比较一下核心函数compare_exchange_weak()compare_exchange_strong() 的区别.

如果在不支持CAS指令的硬件上,compare_exchange_weak() 很可能由于线程切换等原因返回false,即使比较值是一致的(可能是由于本应是原子的CAS操作被切分了).所以weak一般在循环中使用,例如:

bool expected=false;
extern atomic<bool> b; // set somewhere else
while(!b.compare_exchange_weak(expected,true) && !expected);

compare_exchange_strong() 则保证只在原值与预期不一致时返回false,所以没有必要在外部加上循环.


atomic_is_lock_free() 该成员函数用于检查实例化的atomic类型中,操作是否基于锁实现,如果有锁则会产生阻塞.

All atomic types except for std::atomic_flag may be implemented using mutexes or other locking operations, rather than using the lock-free atomic CPU instructions. Atomic types are also allowed to be sometimes lock-free, e.g. if only aligned memory accesses are naturally atomic on a given architecture, misaligned objects of the same type have to use locks.


Memory Order

这部分由于牵涉知识节点较多,无法一一展开,故只记录在理解上容易引发混乱的部分,
相关的具体代码案例网上很多,可自行搜索,也可阅读文尾的链接.

要理解memory_order,首先需要了解术语“happens-before”

它表示的是一种程序员与硬件之间的约定.
假设约定了 A happens before B,
此时无论指令如何进行优化,都需要保证当A先于B发生后,A所带来的一切修改对于B都是可见的.
不同于信号灯所保障的顺序性,这仅是保证了最新的修改结果能正确并且恰如其分的在线程间进行同步.
happens-before not means happening-before!
需要进行这种约定是由于现代处理器的乱序执行和多级存储结构.

总结来说,线程间进行数据交换需要保障两个条件:
1)事件能按预定顺序发生(消除data racing)
2)不同事件发生后所带来的修改能及时恰当的在线程间同步(保证结果可见)

为了助于理解,可以比较一下C11中atomic和mutex的区别
atomic原子型变量,只保证了对于这个变量本身的修改是无需上锁的,如不添加其他设定(即atomic中memory_order设为relaxed),其保护范围是这个变量自身.
mutex锁,保护的则是代码段,其保证的是两段代码同一时间只能有其中一段在被计算.
也就是说, atomic + 正确的memory_order = 锁
(例如文章最下方附带的自旋锁,就是基于此实现)

理解happens-before的关键在于要将此概念与信号灯所保障的”事件能顺序发生”予以区别,happens-before约定的是存在先后关系的修改结果要能及时可见,而非保证事件本身能按顺序发生.

memory_order目前只在进行多线程无锁编程时需要关注.
X86/64构架的CPU为Strong Memory Models,在此类硬件的相关指令中已经隐含了此语意,所以在代码层可以无需考虑.

A strong hardware memory model is one in which every machine instruction comes implicitly with acquire and release semantics. As a result, when one CPU core performs a sequence of writes, every other CPU core sees those values change in the same order that they were written.


对于atomic的操作可以附带memory_order作为参数.
memory_order的六种可选项表述了3种模型:
sequentially consistent
(memory_order_seq_cst)
acquire-release
(memory_order_consume,memory_order_acquire,memory_order_releasememory_order_acq_rel)
relaxed
(memory_order_relaxed)

atomic类中所有操作如果不做指定默认使用memory_order_seq_cst
如果自己指定内存屏障类型
Store操作只能附带
memory_order_relaxed,
memory_order_release, memory_order_seq_cst
Load操作只能附带
memory_order_relaxed,
memory_order_consume,memory_order_acquire,
memory_order_seq_cst
Read_Modify_Write操作能附带所有六种内存屏障

store和load所附带的memory_order应是成对使用的,
例如在一个load(acquire)之前,必须要发生一个store(release),
否则memory_order将不会生效.
这就好比一个lock必须前置一个unlock,否则死锁.


一个基于atomic和memory_order实现的自旋锁

#ifndef CSPINLOCK_H
#define CSPINLOCK_H

#include <atomic>

class Spinlock
{
public:
    Spinlock(){
        //http://en.cppreference.com/w/cpp/atomic/atomic_flag
        //std::atomic_flag lock = ATOMIC_FLAG_INIT;
        //Has error with vs2013.STL
        flag_.clear();
    }

    void lock(){
        while (flag_.test_and_set(std::memory_order_acquire));
    }

    void unlock(){
        flag_.clear(std::memory_order_release);
    }

    Spinlock(const Spinlock&) = delete;
    Spinlock& operator = (const Spinlock&) = delete;

private:
    std::atomic_flag flag_;
};

#endif

值得阅读的相关文章:
Atomic vs. Non-Atomic Operations
The Synchronizes-With Relation
This Is Why They Call It a Weakly-Ordered CPU
Weak vs. Strong Memory Models

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值