当前位置:网站首页>并发程序的隐藏杀手——假共享(False Sharing)
并发程序的隐藏杀手——假共享(False Sharing)
2022-08-04 14:04:00 【InfoQ】
前言
假共享(False Sharing)
缓存行
假共享

- 线程A从三级缓存当中将数据加载到二级缓存和一级缓存然后在CPU- Core0当中执行代码,线程B从三级缓存将数据加载到二级缓存和一级缓存然后在CPU- Core1当中执行代码。
- 线程A不断的执行a += 1,因为线程B缓存的缓存行当中包含数据a,线程A在修改a的值之后,就会在总线上发送消息,让其他处理器当中含有变量a的缓存行失效,在处理器将缓存行失效之后,就会在总线上发送消息,表示缓存行已经失效,线程A所在的CPU- Core0收到消息之后将更新后的数据刷新到三级Cache。
- 这个时候线程B所在的CPU-Core1当中含有a的缓存行已经失效,因为变量b和变量a在同一个缓存行,现在线程B想对变量b进行加一操作,但是在一级和二级缓存当中已经没有了,它需要三级缓存当中加载这个缓存行,如果三级缓存当中没有就需要去内存当中加载。
- 仔细分析上面的过程你就会发现线程B并没有对变量a有什么操作,但是它需要的缓存行就失效了,虽然和线程B共享需要同一个内容的缓存行,但是他们之间并没有真正共享数据,所以这种现象叫做假共享。
Java代码复现假共享
复现假共享
class Data { public volatile long a; public volatile long b;} public class FalseSharing { public static void main(String[] args) throws InterruptedException { Data data = new Data(); long start = System.currentTimeMillis(); Thread A = new Thread(() -> { for (int i = 0; i < 500_000_000; i++) { data.a += 1; } }, "A"); Thread B = new Thread(() -> { for (int i = 0; i < 500_000_000; i++) { data.b += 1; } }, "B"); A.start(); B.start(); A.join(); B.join(); long end = System.currentTimeMillis(); System.out.println("花费时间为:" + (end - start)); System.out.println(data.a); System.out.println(data.b); }}class Data { public volatile long a1, a2, a3, a4, a5, a6, a7; public volatile long a; public volatile long b1, b2, b3, b4, b5, b6, b7; public volatile long b;} public class FalseSharing { public static void main(String[] args) throws InterruptedException { Data data = new Data(); long start = System.currentTimeMillis(); Thread A = new Thread(() -> { for (int i = 0; i < 500_000_000; i++) { data.a += 1; } }, "A"); Thread B = new Thread(() -> { for (int i = 0; i < 500_000_000; i++) { data.b += 1; } }, "B"); A.start(); B.start(); A.join(); B.join(); long end = System.currentTimeMillis(); System.out.println("花费时间为:" + (end - start)); System.out.println(data.a); System.out.println(data.b); }}JDK解决假共享
import sun.misc.Contended; class Data {// public volatile long a1, a2, a3, a4, a5, a6, a7; @Contended public volatile long a;// public volatile long b1, b2, b3, b4, b5, b6, b7; @Contended public volatile long b;} public class FalseSharing { public static void main(String[] args) throws InterruptedException { Data data = new Data(); long start = System.currentTimeMillis(); Thread A = new Thread(() -> { for (long i = 0; i < 500_000_000; i++) { data.a += 1; } }, "A"); Thread B = new Thread(() -> { for (long i = 0; i < 500_000_000; i++) { data.b += 1; } }, "B"); A.start(); B.start(); A.join(); B.join(); long end = System.currentTimeMillis(); System.out.println("花费时间为:" + (end - start)); System.out.println(data.a); System.out.println(data.b); }} - 在我们自己解决假共享的代码当中,是在变量a的左右两边加入56个字节的其他变量,让他和变量b不在同一个缓存行当中。
- 在JDK给我们提供的注解@Contended,是在被加注解的字段的右边加入一定数量的空字节,默认加入128空字节,那么变量a和变量b之间的内存地址大一点,最终不在同一个缓存行当中。这个字节数量可以使用JVM参数-XX:ContendedPaddingWidth=64,进行控制,比如这个是64个字节。
- 除此之外@Contended注解还能够将变量进行分组:
class Data { @Contended("a") public volatile long a; @Contended("bc") public volatile long b; @Contended("bc") public volatile long c;}
OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 20 0a 06 00 (00100000 00001010 00000110 00000000) (395808) 12 132 (alignment/padding gap) 144 8 long Data.a 0 152 128 (alignment/padding gap) 280 8 long Data.b 0 288 8 long Data.c 0 296 128 (loss due to the next object alignment)Instance size: 424 bytesSpace losses: 260 bytes internal + 128 bytes external = 388 bytes totalimport org.openjdk.jol.info.ClassLayout;import sun.misc.Contended; class Data { @Contended("a") public volatile long a; @Contended("bc") public volatile long b; @Contended("bc") public volatile long c;} public class FalseSharing { public static void main(String[] args) throws InterruptedException { Data data = new Data(); System.out.println(ClassLayout.parseInstance(data).toPrintable()); }}
从更低层次C语言看假共享
#include <stdio.h>#include <pthread.h>#include <time.h> #define CHOOSE // 这里定义了 CHOOSE 如果不想定义CHOOSE 则将这一行注释掉即可 // 定义一个全局变量int data[1000]; void* add(void* flag) { // 这个函数的作用就是不断的往 data 当中的某个数据进行加一操作 int idx = *((int *)flag); for (long i = 0; i < 10000000000; ++i) { data[idx]++; }} int main() { pthread_t a, b;#ifdef CHOOSE // 如果定义了 CHOOSE 则执行下面的代码 让两个线程操作的变量隔得远一点 让他们不在同一个缓存行当中 int flag_a = 0; int flag_b = 100; printf("远离\n");#else // 如果没有定义 让他们隔得近一点 也就是说让他们在同一个缓存行当中 int flag_a = 0; int flag_b = 1; printf("临近\n");#endif pthread_create(&a, NULL, add, &flag_a); // 创建线程a 执行函数 add 传递参数 flag_a 并且启动 pthread_create(&b, NULL, add, &flag_b); // 创建线程b 执行函数 add 传递参数 flag_b 并且启动 long start = time(NULL); pthread_join(a, NULL); // 主线程等待线程a执行完成 pthread_join(b, NULL); // 主线程等待线程b执行完成 long end = time(NULL); printf("data[0] = %d\t data[1] = %d\n", data[0], data[1]); printf("cost time = %ld\n", (end - start)); return 0;}
- readl:这个表示真实世界当中的墙钟时间,就是表示这个程序执行所花费的时间,这个秒单位和我们平常说的秒是一样的。
- user:这个表示程序在用户态执行的CPU时间,CPU时间和真实时间是不一样的,这里需要注意区分,这里的秒和我们平常的秒是不一样的。
- sys:这个表示程序在内核态执行所花费的CPU时间。
总结
- 当多个线程操作同一个缓存行当中的多个不同的变量时,虽然他们事实上没有对数据进行共享,但是他们对同一个缓存行当中的数据进行修改,而由于缓存一致性协议的存在会导致程序执行的效率降低,这种现象叫做假共享。
- 在Java程序当中我们如果想让多个变量不在同一个缓存行当中的话,我们可以在变量的旁边通过增加其他变量的方式让多个不同的变量不在同一个缓存行。
- JDK也为我们提供了Contended注解可以在字段的后面通过增加空字节的方式让多个数据不在同一个缓存行,而且你需要在JVM参数当中加入-XX:-RestrictContended,同时你可以通过JVM参数-XX:ContendedPaddingWidth=64调整空字节的数目。JDK8之后注解Contended在JDK当中的位置有所变化,大家可以查询一下。
- 我们也是用了C语言的API去测试了假共享,事实上在Java虚拟机当中底层的线程也是通过调用pthread_create进行创建的。
边栏推荐
- 文字编码 - XML 教程
- 博途200/1500PLC多段曲线控温FB(支持40段控温曲线、段曲线搜索、暂停、跳段等功能)
- 错误 AttributeError type object 'Callable' has no attribute '_abc_registry' 解决方案
- 让Web页面中的编辑器支持黏贴或直接拖拽来添加图片「建议收藏」
- 信创是什么意思?涉及哪些行业?为什么要发展信创?
- 如何通过使用“缓存”相关技术,解决“高并发”的业务场景案例?
- ICML 2022 | 图神经网络的局部增强
- 从理论到实践:MySQL性能优化和高可用架构,一次讲清
- 大势所趋之下的nft拍卖,未来艺术品的新赋能
- Fuse bit of AVR study notes
猜你喜欢

【LeetCode】38、外观数列

Button control switch 4017 digital circuit chip
将 Sentinel 熔断限流规则持久化到 Nacos 配置中心

How to find the location of a pdf file in endnote literature

考研上岸又转行软件测试,从5k到13k完美逆袭,杭州校区小哥哥拒绝平庸终圆梦!

《社会企业开展应聘文职人员培训规范》团体标准在新华书店上架

如何查找endnote文献中pdf文件的位置

浙江大学团队使用基于知识图谱的新方法,从空间分辨转录组数据中推断细胞间通信状况

物联网应用发展趋势

干掉visio,这个画图神器真的绝了
随机推荐
SLAM 04.视觉里程计-1-相机模型
橄榄枝大课堂APP正式启动上线
Utility function---string processing
MySQL性能指标TPS\QPS\IOPS如何压测?
小 P 周刊 Vol.13
router---模式
爬虫——动作链、xpath、打码平台使用
记录都有哪些_js常用方法总结
centos7安装mysql急速版
代码越写越乱?那是因为你没用责任链!
零基础可以转行软件测试吗 ?这篇文章告诉你
"Social Enterprises Conducting Civilian Personnel Training Specifications" group standard on the shelves of Xinhua Bookstore
电子行业MES管理系统有哪些特殊功能
Install mysql on k8s
国家安全机关对涉嫌危害国家安全犯罪嫌疑人杨智渊实施刑事拘传审查
MySQL【触发器】
MPLS experiment
如何才能有效、高效阅读?猿辅导建议“因材因时施教”
vcl啥意思_oval
oracle+RAC+linux5.1所需要安装的包