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

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

相关文章

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后

C语言线程池的常见实现方式详解

《C语言线程池的常见实现方式详解》本文介绍了如何使用C语言实现一个基本的线程池,线程池的实现包括工作线程、任务队列、任务调度、线程池的初始化、任务添加、销毁等步骤,感兴趣的朋友跟随小编一起看看吧... 目录1. 线程池的基本结构2. 线程池的实现步骤3. 线程池的核心数据结构4. 线程池的详细实现4.1 初

Java子线程无法获取Attributes的解决方法(最新推荐)

《Java子线程无法获取Attributes的解决方法(最新推荐)》在Java多线程编程中,子线程无法直接获取主线程设置的Attributes是一个常见问题,本文探讨了这一问题的原因,并提供了两种解决... 目录一、问题原因二、解决方案1. 直接传递数据2. 使用ThreadLocal(适用于线程独立数据)

C#反射编程之GetConstructor()方法解读

《C#反射编程之GetConstructor()方法解读》C#中Type类的GetConstructor()方法用于获取指定类型的构造函数,该方法有多个重载版本,可以根据不同的参数获取不同特性的构造函... 目录C# GetConstructor()方法有4个重载以GetConstructor(Type[]

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

高并发环境中保持幂等性

在高并发环境中保持幂等性是一项重要的挑战。幂等性指的是无论操作执行多少次,其效果都是相同的。确保操作的幂等性可以避免重复执行带来的副作用。以下是一些保持幂等性的常用方法: 唯一标识符: 请求唯一标识:在每次请求中引入唯一标识符(如 UUID 或者生成的唯一 ID),在处理请求时,系统可以检查这个标识符是否已经处理过,如果是,则忽略重复请求。幂等键(Idempotency Key):客户端在每次

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

Go Playground 在线编程环境

For all examples in this and the next chapter, we will use Go Playground. Go Playground represents a web service that can run programs written in Go. It can be opened in a web browser using the follow

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念