多线程编程:伪共享以及其解决方案 - Go语言中文社区

多线程编程:伪共享以及其解决方案


首先本文是根据多篇博客的整合而来,依照本人的理解所写

1.基本概念的了解

回到正题,建议先从下面的博客链接看起以便对下列概念有个基本的了解:

1)CPU缓存
2)MESI协议以及RFO请求
3)缓存行

具体博客链接:https://www.cnblogs.com/cyfonly/p/5800758.html

当然如果你不想读冗长的文章,我这里对上面的概念做一个简单的解释:

1.1 CPU缓存

首先CPU的缓存结构是下面这样的:

假如CPU要计算一个数据,如果在自身的寄存器上找不到,那么它就会从L1,L2,L3,内存这样去找,其依次耗费的时间如下:

在这里插入图片描述

1.2 MESI协议

假如某个数据在CPU核心1号上面有,但是CPU核心2号也想访问,那么该如何做?

一般来说,该由2号直接从1号去拿便是,但是麻烦

在这里插入图片描述
但是更好的方法是让1号直接把数据的副本发一份给2号即可:
在这里插入图片描述
但是只是简单的发过去是不行的,

假如该数据是只读的,如何弄?

或者假如该数据是可以写的,但是只准一个线程写,其他线程不能写又该如何约束?

所以就有了MESI协议:
在这里插入图片描述
四种具体状态的转换如下:
在这里插入图片描述

其中造成伪共享最重要的原因就是远程写

在这里插入图片描述

简单的来说就是:假如核1和核2同时要对 数据A 做写操作,那么核1和核2都会向对方发送RFO(Request For Owner)的请求,阻止对方不要写,但为了调度,那么核1 和 核2 的行为是彼此间隔的,核1不动 核 2 写 , 核2写完后,核1再写,循环…

这样做的话效率就非常非常低了

但是下一个问题就是核1和核2是如何对数据进行界定的?这个也是为什么会产生伪共享的关键

这里引用cyfonly关于缓存行的描述:

缓存系统中是以缓存行(cache line)为单位存储的。缓存行通常是 64 字节(译注:本文基于 64 字节,其他长度的如 32 字节等不适本文讨论的重点),并且它有效地引用主内存中的一块地址。一个 Java 的 long 类型是 8 字节,因此在一个缓存行中可以存 8 个 long 类型的变量。所以,如果你访问一个 long 数组,当数组中的一个值被加载到缓存中,它会额外加载另外 7 个,以致你能非常快地遍历这个数组。事实上,你可以非常快速的遍历在连续的内存块中分配的任意数据结构。而如果你在数据结构中的项在内存中不是彼此相邻的(如链表),你将得不到免费缓存加载所带来的优势,并且在这些数据结构中的每一个项都可能会出现缓存未命中。

简单的来说:CPU读取 缓存 是以 缓存行的形式去读取的

缓存行 是由64个字节组成,不仅是数组数据,还是其他结构的数据也是这样存储的,对,没错,连续存储的,这也是缓存快的原因!

所以问题就来了:

假如有4个线程去修改下面的longs数组中的元素会如何?

假如线程1修改longs[0],线程2修改longs[1],以此类推


	private static VolatileLong[] longs = new VolatileLong[4];

    public final static class VolatileLong {
        public volatile long value = 0L;
	}
  

但是后面会出问题,因为

由于缓存行是连续的,long[0],long[1]…等是在一个缓存行中的!

所以线程在对各自要操作的元素进行写操作的时候,假如有四个核,各自执行对应线程的写操作,读到了连续的缓存行,由于要操作的元素在其中,那么必然避免不了彼此发RFO(Request For Owner)请求!

而这就是典型的伪共享问题!

在这里插入图片描述

如何避免缓存行连续带来的伪共享问题?

简单的来说,核1如果要操作longs[0],核2要操作longs[1],

2.1 解决对策:padding

那么就必然要做到彼此要写的内容不能在同一个缓存行上!

那么常见的方法就是padding(填充),就是填充无用的对象,来避免被缓存到同一个行上

比如上面的VolatileLong可以改为:

public final static class VolatileLong {
        volatile long p0, p1, p2, p3, p4, p5, p6;
        public volatile long value = 0L;
        volatile long q0, q1, q2, q3, q4, q5, q6;
}

这样就算是N个线程操作VolatileLong 的数组的不同元素也不会再发生类似的问题了

2.2 Java8 的Contended

但是如果每个类都需要这样写就很麻烦,

所以这里引入了Java8的Contended注解来解决这个问题:

    @sun.misc.Contended
    public final static class VolatileLong3 {
        public volatile long value = 0L;
    }

附:引入@sun.misc.Contended报错

如果你用的是STS或者Ecplise,可能会有类似的问题,点击项目,remove掉Ecplise自带的JDK,换上自己安装的JDK8即可

具体参考:https://blog.csdn.net/u014471160/article/details/78523440

想要做参照实验的,可以参考:该作者有较为详细的介绍 https://www.jianshu.com/p/c3c108c3dcfd

想明白后面具体的原理,可以参考这篇专家文章,虽然是引用的,但是内容的非常透彻,系统和完整,并且介绍了一个非常牛逼的多线程框架Disruptor:
https://blog.csdn.net/qq_27680317/article/details/78486220?locationNum=5&fps=1

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/fenghuoliuxing990124/article/details/85127135
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-06-28 00:36:16
  • 阅读 ( 973 )
  • 分类:Linux

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢