并发编程(二)
volatile
synchronized 重量级锁 读写都锁住,读和写都是安全的
volatile 轻量级锁,即功能不全的锁 只锁住读,读安全写不安全
它在多处理器开发中保证了共享变量的“可见性”
多处理器是指的 CPU 底端的多核心
可见性的意思是当一个线程
修改一个共享变量时,另外一个线程能读到这个修改的值。
如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不需要阻塞停顿
计算机内部组成
有CPU, 内部有运算核心ALU 单元
内存
内存中存储的都是变量以及函数的运行状态下的数据。
CPU 是要从内存中读取数据进行操作
对CPU进行操作的话,是通过总线向内存中传输指令。
那么总线本质上就是一个导线,总线这一块,我们传输指令就是所谓的1010指令,就是电压信号。而变量信号,从这是不能同时过的。
单根导线上同一时刻只能过一个电压信号。
如果是多根导线,可以同时过多个电压信号,总线的宽度一般是36到41位,在计算机组成原理上讲,就是说并排30多根到40多根的导线。
一个指令,通常来说先传地址,再传数据,差不多一个最基本的指令,也是总线的宽度。
我们不同的指令是不能够同时传的,传的话就是电压信号的相互干扰,
所以指令在总线必须排队传输,既然排队传输就意味着什么?发出指令的时候,有一个指令
队列。
我们的内存和 CPU 的数据是双向的
但是CPU给内存发送的指令比较多,所以指令需要在指令队列进行排队,轮流发过去,也就意味着内存同一时刻只能被一个指令所访问。
现在属于 CPU 和内部的存储一直交互,总线释放了,就可以把其他数据读过来进行操作了。
里边加一个存储,它就支持了多核 CPU 的发展。
最后计算完才一个来回。
它的存储越多,存的数据越多,它对核心的支持也越多。
把每个线程的数据都存在这,存储越大,线程存过来的越多,存储越少,线程存储过来就越少,那么和总线交互的就得越多。这个区域越大,分的数据越多,它对于总线的压力降低就会越明显
这里边存储的大小和咱们性能关乎是比较大的。
这里边有一个什么有叫高速缓存的地方,它有三份缓存,这属于咱们 CPU 内部看到吗?也就说咱们 CPU 内部的话,还有几十兆的大小。
电脑的高速缓存比较大,它的性能一定好
为什么要设计多级缓存?(一般电脑上为三级缓存)
为了加快传输速度
在传输过程中,CPU 是浪费的,利用不充分,为什么会浪费?
在22秒的时间 CPU 没有电压过来,它不运行数据过来之后传了电源信号
内存,咱们看单个核心的计算速度,单个核心是2.3g 赫兹就是一秒钟2.3乘以二的30次方。次一赫兹就是一个周期,11个 G 的话就是二的30次方的话,就是三个102次相乘十亿多一点。2.3g 赫兹,就是23个亿,每秒钟23个亿。总的运算速度是14乘以23,比如说每秒钟300亿次左右,这是 CPU 的基础指令的运算速度。当然我们 CPU 的这个单次。不给的量也不大,就是64比特它决定了咱们操作系统的位数64位操作系统。那么。2.3g 赫兹23亿。我们再看一秒一秒的话是十亿纳秒,一秒是十亿纳秒,这就意味着是对应的是23。23亿十亿纳表一纳秒。一纳秒的话,大概运行的话是2.3次。这样的话,我们可以认为执行一次的话是0.4纳米。其中一个核心执行依次是0.4纳秒。我们的数据,从内存到咱们的预算单元大概是需要多长时间,大概是20万个左右。
当进行长距离网络传输的时候,例如MBA 网络传输的时候,这一根导线上可以同时存在多个电压信号。
电压是怎么产生的?
假设有一根导线,通过某种方式给它加入了大量的电荷,从另一个渠道硬压过来的电荷,比如这个渠道也是高电压,携带大量电荷,一堆电荷无处释放,现在给他连了一根导线,它能通过导线的通道快速把电压往其他地方输送,
它周围都是空气,是分子之间距离比较大,太大的缝隙导致电子无法通过,所以空气是绝缘体,
那木头的话,它本身也是内部的空气缝隙太大,导致电子无法顺利通过。
木头为什么占了水之后它就可以导电?占了水之后它中间的空气没有了,它给电子把路给打通了,所以就导电了。
导线本身的金属,它属于致密性物质,对于电子来说是一个非常容易通过的一条通道,且导线也是分成一段一段的区域。
虽然一个导线能同时存在多个电压信号,但是基本上还是说发送源还得指定是一个,如果多个发送源的话,会相互交错,对于导线来说很难区分。
短距离导线,换成波浪式的一会释放多个电荷,一会释放少量电荷,这边是无法感知到的,它这边传输的时候,这一条导线上就只能有一个电压信号,传过来之后才能传第二个电压信号,这就导致每次电压信号过来的时候都有一个传输。
短距离传输的话 CPU一直处于等待,使用不充分,浪费
在内存和核心之间增加中转站,增加一个中转站就可以视为增加一根导线
这样就会造成发送频率比以前高了很多。数据发到中转站之后,就可以发下一份数据,虽然每个数据都是走的,总时间不变。但是发送频率高了,接收数据的频率也高了,利用率会得到大幅度提升
但是中转站不是越多越好,中间的转发也会耗费时间
所以目前来说,平衡点性能最好的平衡点就是刚好三级缓存。
为什么加三级缓存?
第一,它能支持多核 CPU,
那为什么不设置一个缓存,而非得设置三个?
提升每个核心的利用率。高速缓存支持的多核 CPU,多个核心,设置多级缓冲能提升每个核心的利用率
l3高速缓存的话,距离核心的话基本上是一纳秒的距离
首先,电流在导线中的传播速度在铜线中是16万千米,在金线中是20万千米。有的 CPU 内部就是用金子做的金子的延展性比较好,可以拉丝拉的特别细,一克金可以做很多 CPU
芯片里边是多级缓存,它的传输过程中,有个传输间隔。有
传输间隔的话,CPU 就不容易使用率打满
就算电脑特别卡顿,CPU 的使用率还是很难达满的。
但是内存倒是容易打满,一般来说,卡顿的情况通常来说是内存不够
为什么讲高速缓存?
因为它还给咱们带来了并发性的问题,多个线程操作不同的变量,但是线程是可以操作同一个变量的,可能会导致相互覆盖问题。
更新随机
比如说两个线程各对a=0进行加一万次。
他们有可能加到一万次再往回更新,也可能加了六次就会更新。
更新完之后再重新读,再接着加到2000更新
所以不一定是计算完之后才往回更新,注意,往回更新是随机的。
由什么决定?
高速缓存,假设存储容量大概几十兆。满的情况下会出发往回返回。
如果缓存不够的情况下会往回更新,什么时候不够也不知道,所以最终结果不确定
计算往回更新的时候,高速缓存会清空数据,缓存这块没有数据了,下次再计算,还得从内存接着往缓存读,以此类推
CPU中的部分术语
补充介绍:
内存屏障
就是约定好不能读哪块内存,
怎么约定?
在内存中有一个变量标记,这个变量变成特定值时,可以访问,变成其他不让访问。
比如一个约定,它只是调内存屏障,表示见到这个标记就不要去访问了
缓冲行
高速缓存的基本存储单元
计算机里的存储是一份一份的。
计算机具有存储能力的区域有哪些?
第一,内存
第二,硬盘
第三,CPU 内部。
CPU 内部,就是高速缓存,这三个区域的数据都是一份份的,
硬盘和内存中的数据单元是4KB。
计算机中最小的物理单元是一字节为一个存储单元,但是操作系统和硬盘操作系统给硬盘和内存。划分的逻辑存储就是每4KB为一个存储区域。
在硬盘上,一个存储单元是栈区
在内存中,一个存储单元是一个页面
在高速缓存中,存储单元是一个缓冲行,一个缓冲行64字节。
也就说,把高速缓存放大,里边是由一个个的缓冲行组成的,能存64字节的数据。
每一个缓冲行和外界相连的导线线路进行数据交互。
处理器填写缓存线时会加载整个缓存线,要使用多个主内存读周期。
一个周期从内存中读数据,就叫一个主内存读周期,需要多个留存周期就是时间的意思。
字节比较小,一操作就把它整个全都加载了,处理更新完之后整个都放进来
由于它每个缓存行的话,都有一条唯一路线,
唯一路线的话,其中一个任务占着线操作的时候,其他任务就不能对它进行操作了。
因为这条线是个唯一通道,一旦占据这根线,相当于占了整个,对它操作完成后释放之后,其他任务才能过来操作,
所以每个任务过来操作的时候,都相当于独占整个缓存行
一个缓存的话是可以存多份数据的,它们可能是不同任务的数据。
不同的芯片内部的缓存行也不一样,有的加载整个,有的加载一部分。
不管加载整个还是加载一部分,对它进行操作的时候就是独占,
毕竟物理信号的限制,所以,如果缓存行里边存的多个任务的数据,多个任务同时读的时候只能有一个进来,其他的就得等着,这样的话会造成一定的性能损耗
原子操作
指一系列操作要么都成功,要么都失败,多个步骤中如果有一个步骤失败,其他步骤都要还原
缓存行填充
操作系统只认 C 语言,同样,硬件,也只认 C 语言。
不管是 java 还是 python 写的各种数据,最终都要转换成 C 语言。
定义的各种复杂的对象类型,其实最后都会转成 C 语言的基本类型。也就是说,在电脑底层其实只存在 C 语言的基本类型。
所以底层运转的数据其实都是由六种数据组成的
既然涉及到底层的只有六种数据,也就是说,缓存行里边存的其实都是基本类型的数据
从一字节到八字节的数据,里边就可以存很多这种数据,那么存的时候,里边数据越多,越容易产生拥堵
就是许多数据一起读或者对它存的时候或读的时候就需要排队。
排队,就有损耗,存的数据越多,排队概率就越大,反之,排队的概率就越小。
这样的话,它里边数据就不多,它的竞争就少一些了
数据量较少
往里边存的时候,哪怕只存一份数据,但是把其他的地方全都用其他数据填充,这样导致整个缓存行只有一份数据,去读它的时候,没有阻拦,一读就能读到,也能大幅度提升性能
但这种情况下,在数据量少的时候没事,数据量多的时候,高速缓存存储的数据不多会导致整体性能变慢,所以该方法应用的场景就是数据量不多的时候
数据量多
数据量大的时候,里边数据太多的话,导致拥塞也不好。这个关键是看咱们一个设计的一个折中点,这为什么说咱们整个缓存行只涉及64字节,不要涉及太大,飞机太大,里边拥堵就更高,反而读起来慢,还不如从内存直接读?每个缓存行小一点,少一点的话,你往里边存一个或者几个数据不会太多,即便拥堵的话也不会特别拥堵。你要追求极限性能的话,你往里边存数据直接是存64g,整个数据全都是你的。每次读都毫无阻碍。是性能最好的时刻。