并发编程之可见性有序性

并发编程之可见性有序性

一、概述

为了提升处理性能所做的优化(对之前写的JMM硬件层面分析补充):

在整个计算机的发展历程中,除了CPU、内存以及I/O设备不断迭代升级来提升计算机处理性能之外,还有一个非常核心的矛盾点,就是这三者在处理速度的差异。CPU的计算速度是非常快的,其次是内存、最后是IO设备(比如磁盘),也就是CPU的计算速度远远高于内存以及磁盘设备的I/O速度。

计算机是利用CPU进行数据运算的,但是CPU只能对内存中的数据进行运算,对于磁盘中的数据,必须要先读取到内存,CPU才能进行运算,也就是CPU和内存之间无法避免的出现了IO操作。

而cpu的运算速度远远高于内存的IO速度,比如在一台2.4GHz的cpu上,每秒能处理2.4x10的9次方,如果是64位操作系统,那么意味着每次能处理64位数据量。

虽然CPU从单核升级到多核甚至到超线程技术在最大化的提高CPU的处理性能,但是仅仅提升CPU性能是不够的,如果内存和磁盘的处理性能没有跟上,就意味着整体的计算效率取决于最慢的设备,为了平衡这三者之间的速度差异,最大化的利用CPU。所以在硬件层面、操作系统层面、编译器层面做出了很多的优化

  1. CPU增加了高速缓存// 导致可见性问题
  2. 操作系统增加了进程、线程。通过CPU的时间片切换最大化的提升CPU的使用率// 导致原子性问题
  3. 编译器的指令优化和CPU重排序,更合理的去利用好CPU的高速缓存// 导致有序性问题

每一种优化,都会带来相应的问题,而这些问题是导致线程安全性问题的根源。

二、问题的根源

1、CPU层面的缓存

CPU在做计算时,和内存的IO操作是无法避免的,而这个IO过程相对于CPU的计算速度来说是非常耗时,基于这样一个问题,所以在CPU层面设计了高速缓存,这个缓存行可以缓存存储在内存中的数据,CPU每次会先从缓存行中读取需要运算的数据,如果缓存行中不存在该数据,才会从内存中加载,通过这样一个机制可以减少CPU和内存的交互开销从而提升CPU的利用率。

2、缓存一致性问题

CPU高速缓存的出现,虽然提升了CPU的利用率,但是同时也带来了另外一个问题–缓存一致性问题,这个一致性问题体现在。
在多线程环境中,当多个线程并行执行加载同一块内存数据时,由于每个CPU都有自己独立的L1、L2缓存,所以每个CPU的这部分缓存空间都会缓存到相同的数据,并且每个CPU执行相关指令时,彼此之间不可见,就会导致缓存的一致性问题。

3、缓存一致性协议

为了解决cpu的缓存一致性;cpu层面提供了总线锁和缓存锁

总线锁
处理器提供的一个LOCK信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占共享内存。这样的做法代价十分昂贵,于是为了降低锁粒度,CPU引入了缓存锁。
当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行时,会使用总线锁。

缓存锁
缓存锁的核心机制就是基于缓存一致性协议来实现的,即一个处理器的缓存回写到内存会导致其他处理器的缓存无效,MESI是一种比较常见的缓存一致性协议实现

  1. M(Modify) 表示共享数据只缓存在当前CPU缓存中,并且是被修改状态,也就是缓存的数据和主内存中的数据不一致
  2. E(Exclusive 表示缓存的独占状态,数据只缓存在当前CPU缓存中,并且没有被修改
  3. S(Shared) 表示数据可能被多个CPU缓存,并且各个缓存中的数据和主内存数据一致
  4. I(Invalid[) 表示缓存已经失效

但是这样会引起多cpu在缓存同步期间带来的阻塞问题。

4、 异步优化(Store Bufferes、invalidate queue)

为了解决cpu因为总线锁或缓存锁导致的阻塞问题,cpu引入了store buffer、invalidate queue

Store Bufferes
基于存储缓存Store Bufferes,CPU将要写入内存数据先写入Store Bufferes中,同时发送消息,然后就可以继续处理其他指令了。当收到所有其他CPU的失效确认(Invalidate Acknowledge)时,数据才会最终被提交。
Store Bufferes的引入提升了CPU的利用效率,但又带来了新的问题:缓存中的数据并不是最新的,所以CPU需要先读取Store Bufferes中是否有值。如果有则直接读取,如果没有再到自己缓存中读取,这就是所谓的”Store Forward“。

invalidate queue失效队列
CPU将数据写入Store Bufferes的同时还会发消息给其他CPU,由于Store Bufferes空间较小,且其他CPU可能正在处理其他事情,没办法及时回复,这个消息就会陷入等待。为了避免接收消息的CPU无法及时处理Invalid失效数据的消息,造成CPU指令等待,就在接收CPU中添加了一个异步消息队列。消息发送方将数据失效消息发送到这个队列中,接收CPU返回已接收,发送方CPU就可以继续执行后续操作了。而接收方CPU再慢慢处理”失效队列“中的消息。

但是这样会引起指令执行的顺序性问题,并造成可见性问题。

5、内存屏障

CPU在性能优化道路上导致的顺序一致性问题,在CPU层面无法被解决,原因是CPU只是一个运算工具,它只接收指令并且执行指令,并不清楚当前执行的整个逻辑中是否存在不能优化的问题,也就是说硬件层面也无法优化这种顺序一致性带来的可见性问题。

为了解决指令执行的顺序性问题, 在CPU层面提供了写屏障、读屏障、全屏障这样的指令,在x86架构中,这三种指令分别是
SFENCE、LFENCE、MFENCE指令

  • sfence:也就是save fence,写屏障指令。在sfence指令前的写操作必须在sfence指令后的写操作 前完成。
  • lfence:也就是load fence,读屏障指令。在lfence指令前的读操作必须在lfence指令后的读操作 前完成。
  • mfence:也就是modify/mix,混合屏障指令,在mfence前得读写操作必须在mfence指令后的读 写操作前完成。

在Linux系统中,将这三种指令分别封装成了, smp_wmb-写屏障 、 smp_rmb-读屏障 、 smp_mb-读写屏障 三个方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值