并发编程线程安全性之可见性有序性

2024-02-26 23:52

本文主要是介绍并发编程线程安全性之可见性有序性,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

可见性

可见性: 就是说一个线程对共享变量的修改,另一个线程能够立刻看到

通俗点说,就是两个线程共享一个变量,无论哪一个线程修改了这个变量,另外一个线程都能够立刻看到上一个线程对这个变量的修改

产生线程安全问题的原因

计算机是利用CPU进行数据运算的,但是CPU只能对内存中的数据进行运算,对于磁盘中的数据,必须要先读取到内存,CPU才能进行运算。cpu,内存,磁盘都会影响计算机的处理性能,同时这三者之间有个核心的矛盾点,就是三者在处理速度上的差异。CPU的计算速度是非常快的,其次是内存、最后是IO设备(比如磁盘),也就是说CPU的计算速度是远远高于内存以及磁盘设备的I/O速度的。

为了平衡这三者之间的速度差异,最大化的利用CPU。所以在硬件层面、操作系统层面、编译器层面做出了很多的优化

  • CPU增加了高速缓存
  • 操作系统增加了进程、线程。通过CPU的时间片切换最大化的提升CPU的使用率
  • 编译器的指令优化,更合理的去利用好CPU的高速缓存

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

CPU高速缓存

解决的问题:CPU高速缓存的出现主要是为了解决CPU运算速度和内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。

打开任务管理器  可以看到CPU的3个缓存

思考: 有了高速缓存后会带来什么问题?

分析:CPU0和CPU1并行处理同时从内存中加载数据到缓存中,此时CPU0改变了这个值,会再同步到内存中,CPU1什么时候再读到这个最新的值是不确定的,因此会存在缓存一致性问题,那么应该怎么解决呢?

在解决这个问题之前,先来了解以下伪共享和缓存行填充

伪共享和缓存行填充

缓存行:CPU的缓存是由多个缓存行组成的,最小交互单元

伪共享:

当有两个线程读取同一缓存行的不同值时,就会去竞争这同一缓存行,这就是伪共享问题。

思考:怎么解决?对齐填充

缓存一致性问题和缓存一致性协议

  • 总线锁
  • 缓存锁
  • 缓存一致性协议(MESI  MOSI) MESI表示缓存的四种状态 修改 失效 独占 共享

如果是S状态 修改时 要先把其它缓存设置为失效 失效状态从内存中读

snoopy协议会监听总线上的事件

假设a=1这个数据要进行修改,刚读进来是共享状态,当需要修改时,会通过总线发送一个指令,让其它缓存失效,然后修改完后,其它缓存从内存中读数据,就解决了缓存一致性的问题。

CPU指令重排序 

cpu层面是如何导致指令重排序的? 如下图

CPU0要写入一个数据时,其它CPU在失效的时候,CPU0是处于阻塞状态的,要等到所有的缓存行失效后,再做写入操作,保证缓存一致性。 异步的思想就是会把数据加载到storebuffer中,继续执行其它的指令,等到其它的缓存行都失效后,再把数据从store buffe加载出来到缓存行

这段代码是如何存在指令重排序的

当CPU从内存中加载a=1时,会先加载到store buffer中,会让其它的缓存行失效,然后当前缓存行中的a=0(缓存一致性)变更为独占状态。然后CPU0从内存中读取b=0放到缓存行中,然后执行b=A+1此时计算出来的结果为1,然后此时其它CPU全部失效了,这时候CPU0再从store buffer中把a=1加载到缓存行,就造成了指令的重排序。

Store Forwarding

如何优化指令重排序?store forwarding就是CPU0从缓存行中读取数据,那么按照上面的例子,b=a+1加载到缓存行的数据,就是a=1了

分析: 假设a=0 存在于cpu1的缓存行中,b=0存在于cpu0的缓存行中 且都为独占状态。cpu0执行的两个代码为写操作 更新操作 CPU1只进行读。当CPU读取b的值时,就向cpu0发起请求,此时cpu0将b变更后设为共享状态。紧接着 a就在cpu1中,因此断言失败,此时 CPU1收到了CPU0的读请求,于是才让a=0缓存行失效,然后加载到cpu0中并变为独占状态后 在进行修改 。

引出的问题怎么办?

Invalid Queue

这个图就相当于 让其它缓存行失效的过程, 由同步 变为了 异步 直接丢到了invalidate中进行处理 提高效率。

CPU性能的博弈之路:

内存屏障

CPU层面不知道  什么时候不允许优化  什么时候优化

  • 读屏障
  • 写屏障
  • 全屏障

Lock: 缓存锁总线锁

在不同的CPU架构中,实现内存屏障的指令不同

JMM模型

Happens-Before模型(告诉你哪些场景不会存在指令重排序问题)

并不是所有的程序指令都会存在可见性或者指令重排序问题,其实质性描述的是可见性规则

规则1:程序顺序型规则(as-if-serial)

不管程序如何重排序,单线程的执行结果一定不会发生变化

规则2:传递性规则

如果A happens before B     B happens before C   那么A happens before C成立

规则3:volatile变量规则

规则4:监视器锁规则

规则5:Start规则

规则6:join规则

总结

可见性导致的原因 1.CPU的高速缓存 2.指令重排序

MESI协议保证缓存的一致性

指令重排序在不同的架构中有着不同的内存屏障指令来解决

这篇关于并发编程线程安全性之可见性有序性的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/750569

相关文章

Linux线程同步/互斥过程详解

《Linux线程同步/互斥过程详解》文章讲解多线程并发访问导致竞态条件,需通过互斥锁、原子操作和条件变量实现线程安全与同步,分析死锁条件及避免方法,并介绍RAII封装技术提升资源管理效率... 目录01. 资源共享问题1.1 多线程并发访问1.2 临界区与临界资源1.3 锁的引入02. 多线程案例2.1 为

go动态限制并发数量的实现示例

《go动态限制并发数量的实现示例》本文主要介绍了Go并发控制方法,通过带缓冲通道和第三方库实现并发数量限制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录带有缓冲大小的通道使用第三方库其他控制并发的方法因为go从语言层面支持并发,所以面试百分百会问到

Go语言并发之通知退出机制的实现

《Go语言并发之通知退出机制的实现》本文主要介绍了Go语言并发之通知退出机制的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、通知退出机制1.1 进程/main函数退出1.2 通过channel退出1.3 通过cont

java如何实现高并发场景下三级缓存的数据一致性

《java如何实现高并发场景下三级缓存的数据一致性》这篇文章主要为大家详细介绍了java如何实现高并发场景下三级缓存的数据一致性,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 下面代码是一个使用Java和Redisson实现的三级缓存服务,主要功能包括:1.缓存结构:本地缓存:使

Java中的xxl-job调度器线程池工作机制

《Java中的xxl-job调度器线程池工作机制》xxl-job通过快慢线程池分离短时与长时任务,动态降级超时任务至慢池,结合异步触发和资源隔离机制,提升高频调度的性能与稳定性,支撑高并发场景下的可靠... 目录⚙️ 一、调度器线程池的核心设计 二、线程池的工作流程 三、线程池配置参数与优化 四、总结:线程

WinForm跨线程访问UI及UI卡死的解决方案

《WinForm跨线程访问UI及UI卡死的解决方案》在WinForm开发过程中,跨线程访问UI控件和界面卡死是常见的技术难题,由于Windows窗体应用程序的UI控件默认只能在主线程(UI线程)上操作... 目录前言正文案例1:直接线程操作(无UI访问)案例2:BeginInvoke访问UI(错误用法)案例

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected