原子操作与锁

1 原子性

1.1 CPU缓存

在这里插入图片描述

L1、L2:一级缓存、二级缓存,均为核心独有
L3:三级缓存,多个核心共用

多级缓存,弥补CPU与内存速度不匹配的问题

1.2 cache line

缓存进行管理的一个最小存储单元,缓存块

在这里插入图片描述

1.3 CPU读写数据

写直达策略

缓存中任何一个字节的修改,都会立刻传播到内存

效率很低

写回策略

写操作:

  • 首先判断是否命中缓存,若命中,直接写在缓存中,并标记为脏数据
  • 若没有命中缓存,则通过LRU算法定位一块新的缓存块,若其为脏数据,则刷到主存,并写入新的数据,标记为脏;若不为脏数据,则直接写入新的数据,标记为

读操作:

  • 首先判断是否命中缓存,命中直接返回
  • 若没有命中缓存,则通过LRU算法定位一块新的缓存块,若其为脏数据,则刷到主存,并写入新的数据,标记为非脏;若不为脏数据,则直接写入新的数据,标记为非脏

对于多个核心的情况下,写回策略存在问题:

假如一个处理器有两个核心,分别是A、B,A中写入i=10,此时内存中的数据为i=5,此时B读取数据,B直接从内存中获取数据,获取到i=5,即读到了不一致的数据

写传播 & 事务串行化

为了解决上述问题,提出了写传播事务串行化

写传播:
在处理器1中的A核心的操作,可以传播到B核心和其他处理器中。

主要是通过总线嗅探bus snooping的方式,即监听是否有数据变的。

事务串行化:
多个处理器对同一个值进行修改,在同一时刻只能有一个处理器写成功,必须保证写操作的原子性,多个写操作必须串行执行。

主要是通过lock指令来实现。

MESI协议

上述写传播中,我们并不需要把数据广播到所有处理器中,因为有时候用不上,所以提出了MESI协议。

主要原理:通过总线嗅探策略(将读写请求通过总线广播给所有核心,核心根据本地状态进行响应)

状态:

  • Modified(M):已修改,某数据已修改但是没有同步到内存中。如果其他核心要读该数据,需要将该数据从缓存同步到内存中,并将状态转为 S。
  • Exclusive(E):某数据只在该核心当中,此时缓存和内存中的数据一致。
  • Shared(S):某数据在多个核心中,此时缓存和内存中的数据一致。
  • Invaliddate(I):某数据在该核心中以失效,不是最新数据。

事件:

  • PrRd:核心请求从缓存块中读出数据; Process Read
  • PrWr:核心请求向缓存块写入数据;
  • BusRd:总线嗅探器收到来自其他核心的读出缓存请求;
  • BusRdX:总线嗅探器收到另一核心写⼀个其不拥有的缓存块的请求;
  • BusUpgr:总线嗅探器收到另一核心写⼀个其拥有的缓存块的请求;
  • Flush:总线嗅探器收到另一核心把一个缓存块写回到主存的请求;
  • FlushOpt:总线嗅探器收到一个缓存块被放置在总线以提供给另一核心的请求,和 Flush 类似,但只不过是从缓存到缓存的传输请求。

状态机:
在这里插入图片描述

写后锁住ME状态,避免相关内存的访问

1.4 原子性的实现

原子操作:多线程环境下,确保对共享变量的操作在执行时不会被干扰,从而避免竞态条件。

对于多处理器多核心,要保证原子性:

  • 操作指令不被打断(关中断)
  • lock指令只需阻止其他核心对相关内存空间的访问
  • MESI协议

2 锁

2.1 互斥锁

独占锁,当线程A获得锁后,线程B无法获得锁,只有A释放锁后才能获得锁。

互斥锁在被占用时,其他线程先在用户态自旋一会,若获取锁失败,会任务挂起,进入阻塞队列(内核中)。

锁释放后,线程从阻塞队列中取出,转换为就绪态。

2.2 自旋锁

自旋锁在发生资源冲突时,原地等待,转换为就绪态。

2.3 原子变量

  • std::atomic<T>:声明原子变量
  • is_lock_free:是否支持无锁操作,只有atomic_flag是无锁操作,只有true或flag,对应一个字节
  • store(T desired, std::memory_order order) :用于将指定的值存储到原子对象中
  • load(std::memory_order order):用于获取原子变量的当前值
  • exchange(std::atomic<T>* obj, T desired)obj参数指向需要替换值的atomic对象,desired参数为期望替换成的值。如果替换成功,则返回原来的值。
  • compare_exchange_weak(T& expected, T val, memory_order success, memory_order failure) :比较一个值和一个期望值是否相等,如果相等则将该值替换成一个新值,并返回 true;否则不做任何操作并返回 false。
    注意,compare_exchange_weak 函数是一个弱化版本的原子操作函数,因为在某些平台上它可能会失败并重试。如果需要保证严格的原子性,则应该使用 compare_exchange_strong 函数。
  • compare_exchange_strong(T& expected, T val, memory_order success, memory_order failure)

3 内存序

由于编译器有时候会对指令进行优化重排,CPU也会对指令进行重排,但有时候可能产生问题。

内存序规定了多个线程访问同一个内存地址时的语义

  • 某个线程对内存地址的更新何时能被其它线程看见
  • 某个线程对内存地址访问附近可以做怎么样的优化

memory_order_relaxed

松散内存序,只用来保证对原子对象的操作是原子的,在不需要保证顺序时使用

在这里插入图片描述

memory_order_release

在写入某原子对象时,当前线程的任何前面的读写操作都不允许重排到这个操作的后面去

在这里插入图片描述

memory_order_acquire

在读取某原子对象时,当前线程的任何后面的读写操作都不允许重排到这个操作的前面去

在这里插入图片描述

memory_order_acq_rel

一个读‐修改‐写操作同时具有获得语义和释放语义,即它前后的任何读写操作都不允许重排

memory_order_seq_cst

顺序一致性语义

对于读操作相当于获得,对于写操作相当于释放,对于读‐修改‐写操作相当于获得释放

是所有原子操作的默认内存序,并且会对所有使用此模型的原子操作建立一个全局顺序,保证了多个原子变量的操作在所有线程里观察到的操作顺序相同,当然它是最慢的同步模型。

  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值