从CPU的视角看 多线程代码为什么那么难写!

这篇具有很好参考价值的文章主要介绍了从CPU的视角看 多线程代码为什么那么难写!。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  当我们提到多线程、并发的时候,我们就会回想起各种诡异的bug,比如各种线程安全问题甚至是应用崩溃,而且这些诡异的bug还很难复现。我们不禁发出了灵魂拷问 “为什么代码测试环境运行好好的,一上线就不行了?”。 为了解决线程安全的问题,我们的先辈们在编程语言中引入了各种各样新名词,就拿我们熟悉的Java为例,不仅java语言自带了synchronized、volatile、wait、notify… ,jdk中各种工具包也是层出不穷,就比如单一个Lock,就可以有很多种实现,甚至很多人都谈锁色变。

  为什么会出现这种情况,我们得先从CPU和主存(RAM)的关系说起。 上个世纪80年代,PC机兴起的时候,CPU的运算速度只有不到1MHz。放现在你桌上的计算器都可以吊打了它了。那时候就是因为CPU运算慢,它对数据存取速度的要求也不那么高,顶多也就1微秒(1000ns)取一次数据,一次访存100ns对CPU来说也算不上什么。 然而这么多年过去了,CPU一直在沿着摩尔定律的道路一路狂奔,而内存访问延迟的速度却一直止步不前。(当然存储也有非常大的发展,但主要体现在容量方面,而访问延时自诞生初就没什么变化)。

  我们来对比下CPU和内存过去几十年之间的发展速率:
从CPU的视角看 多线程代码为什么那么难写!

  可以看出,在过去40年里, CPU的运算速度增量了上千倍,而内存的访问延时却没有太大的变化。 我们就拿当今最先进CPU和内存举例,目前商用的CPU主频基本都是3GHz左右的(其实十多年前基本上就这个水平了),算下来CPU每做一次运算仅需0.3ns(纳秒)。而当前最先进的内存,访问延迟是100ns左右的,中间相差300倍。如果把CPU比作一个打工人的话,那么他的工作状态就会是干一天活然后休一年,这休息的一年里等着内存里的数据过来(真是令人羡慕啊)。

  其实CPU的设计者早就意识到了这点,如果CPU真是干1休300的话,未免也太不高效了。在说具体解决方案前,我这里先额外说下内存,很多人会好奇为什么主存(RAM)的访问速度一直上不来? 这个准确来说其实只是DRAM内存的速度上不了。存储芯片的实现方式有两种,分别是DRAM和SRAM,SRAM的速度其实也一直尽可能跟着CPU在跑的。那为什么不用SRAM来制造内存?这个也很简单,就是因为它存储密度低而且巨贵(相对于DRAM),所以出于成本考量现在内存条都是采用DRAM的技术制造的。

  SRAM容量小成本高,但速度快,DRAM容量大成本低,但速度慢。这俩能不能搭配使用,取长补短?结论是肯定的,在计算机科学里有个”局部性原理“,这个原理是计算机科学领域所有优化的基石。我这里就单从数据访问的局部性来说,某个位置的数据被访问,那么相邻于这个位置的数据更容易被访问。那么利用这点,我们是不是可以把当前最可能被用到的小部分数据存储在SRAM里,而其他的部分继续保留在DRAM中,用很小的一块SRAM来当DRAM的缓存,基于这个思路,于是CPU芯片里就有了Cache,CPU的设计者们觉得一层缓存不够,那就给缓存再加一层缓存,于是大家就看到现在的CPU里有了所谓的什么L1 Cache、L2 Cache, L3 Cache。

  存储示意图如下,真实CPU如右图(Intel I7某型号实物图):
从CPU的视角看 多线程代码为什么那么难写!

  多级缓存的出现,极大程度解决了主存访问速度和CPU运算速度的矛盾,但这种设计也带来了一个新的问题。CPU运算时不直接和主存做数据交互,而是和L1 Cache交互,L1 cache 又是和L2 Cache交互…… 那么一定意味着同一份数据被缓存了多份,各层存储之间的数据一致性如何保证? 如果是单线程还好,毕竟查询同一时间只会在一个核心上运行。但当多线程需要操作同一份数据时,数据一致性的问题就凸显出来了,如下图,我们举个例子。
从CPU的视角看 多线程代码为什么那么难写!
  在上图中3个CPU核心各自的Cache分别持有了不同的a0值(先忽略E和I标记),实际上只有Cache0里才持有正确的数值。这时候,如果CPU1或者CPU2需要拿着Cache中a0值去执行某些操作,那结果可想而知。如果想保证程序在多线程环境下正确运行,就首先得保证Cache里的数据能在"恰当"的时间失效,并且有效的数据也能被及时回写到主存里。

  然而CPU是不知道当前时刻下哪些数据该失效、哪些该回写、哪些又是可以接着使用的。这个时候其实CPU的设计者也很犯难,如果数据频繁失效,CPU每次获取必须从主存里获取数据,CPU实际运算能力将回到几十年前的水平。如果一直不给不失效,就会出现数据不一致导致的问题。于是CPU的设计者不干了:”这个问题我处理不了,我给你们提供一些可以保证数据一致性的汇编指令,你们自己去处理”。 于是大家就在intel、arm的开发手册上看到了像xchg、lock、flush……之类的汇编指令,C/C++语言和操作系统的开发者将这些封装成了volatile、atomic……以及各种系统调用,JVM和JDK的开发者又把这些封装了我在文首说的那一堆关键词。 于是CPU的设计者为了提升性能导致数据一致性的问题,最终还是推给了上层开发者自己去解决。

  作为上层的开发者们(比如我们)就得判断,在多线程环境下那些数据操作必须是原子操作的,这个时候必须使用Unsafe.compareAndSwap()来操作。还有那些数据是不能被CPU Cache缓存的,这个时候就得加volatile关键词。极端情况下,你可以所有的操作搞成原子操作、所有的变量都声明成volatile,虽然这样的确可以保证线程安全,但也会因为主存访问延时的问题,显著降低代码运行的速度。这个时候局部性原理又发挥出其神奇的价值,在实际情况下,绝大多数场景都是线程安全的,我们只需要保证某些关键操作的线程安全性即可。举个简单的例子,我们在任务向多线程分发的时候,只需要保证一个任务同时只被分发给一个线程即可,而不需要保证整个任务执行的过程都是完全线程安全的。

  作为Java开发者,Java和JDK的开发者们已经帮我们在很多场景下封装好了这些工具,比如我们就拿ReentrantLock实现一个多线程计数器的例子来看。
从CPU的视角看 多线程代码为什么那么难写!

  其中increment() 本身不是一个线程安全的方法,如果多个线程并发去调用,仍然会出现count值增长不准确的问题。但在lock的加持下,我们能保证increment()方法同时只能有一个线程在执行。想象下,如果我们把上述代码中的counter()方法换成一些更复杂的方法,而完全不需要在方法中去考虑线程安全的问题,这不就实现了仅在关键操作上保证准确性就能保证全局的线程安全吗!而当我们去深究lock的实现时,就会发现它底层也只是在tryAcquire中使用CAS设置了state值。
从CPU的视角看 多线程代码为什么那么难写!
  在多线程编程中,加锁或加同步其实是最简单的,但是在什么时候什么地方加锁却是一件非常复杂的事情。你需要考虑锁的粒度的问题,粒度太大可能影响性能,粒度过小可能导致线程安全的问题。还需要考虑到加锁顺序的问题,加锁顺序不当可能会导致死锁。还要考虑数据同步的问题,同步的数据越多,CPU Cache带来的性能提升也就越少……

  从上面CPU的发展变化我们可以看到,现代CPU的本质其实也是一个分布式系统,很多时候仍需要编程者手动去解决数据不一致性的问题。当然随着编程语言的发展,这些底层相关的东西也逐渐对普通程序员变得更透明化,我们是不是可以预想,未来是不是会有一门高性能、并且完全不需要程序员关注数据一致性的编程语言出现?

  最后上面计数器代码给大家留一个思考题: 代码中的counter变量声明是否需要加volatile关键字?文章来源地址https://www.toymoban.com/news/detail-430545.html

到了这里,关于从CPU的视角看 多线程代码为什么那么难写!的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ElasticSearch(七):ES查询速度为什么那么快

    介绍给大家一个开源SpringCloud项目。整合了大部分开源中间件,详情信息可以查看文档: spring cloud开源组件开发 另外自己以后博客所讲解的代码内容,都会我的Git上同步(GitHub同步)GIT地址 ES使用的数据结构是倒排索引,在对搜索内容进行分词的时候,会根据搜索内容分词结

    2023年04月08日
    浏览(68)
  • 软文发稿平台那么多,为什么选择媒介盒子

    近年来随着互联网技术的发展,越来越多的企业开始注重软文营销,品牌软文推广对企业来说是至关重要的,也有许多企业选择和软文发稿平台合作来增强品牌曝光,提升宣传效果,那么为什么会有这么多企业选择媒介盒子合作呢,接下来就由小编告诉大家。 一、 传统软文

    2024年02月09日
    浏览(30)
  • CentOS软件那么老为什么大家还要用它?

    作为一个专业的服务器系统,RHEL 系统理论上每一个软件包都有 RedHat 内部的人员负责维护,这个维护包括长期(和系统生命周期一样长)的开发、更新、测试、运维等。也就是说你能从 RHEL 系统源上获得的每一个软件包,出现问题都可以找 RedHat 负责。所以 RHEL 不可能无限制

    2024年02月01日
    浏览(42)
  • ElasticSearch第七讲 ES查询速度为什么那么快

    介绍给大家一个开源SpringCloud项目。整合了大部分开源中间件,详情信息可以查看文档: spring cloud开源组件开发 另外自己以后博客所讲解的代码内容,都会我的Git上同步(GitHub同步)GIT地址 ES使用的数据结构是倒排索引,在对搜索内容进行分词的时候,会根据搜索内容分词结

    2023年04月25日
    浏览(40)
  • ElasticSearch第七讲:ES查询速度为什么那么快

    介绍给大家一个开源SpringCloud项目。整合了大部分开源中间件,详情信息可以查看文档: spring cloud开源组件开发 另外自己以后博客所讲解的代码内容,都会我的Git上同步(GitHub同步)GIT地址 ES使用的数据结构是倒排索引,在对搜索内容进行分词的时候,会根据搜索内容分词结

    2023年04月19日
    浏览(41)
  • 【AI学习】Transformer的Token嵌入表示为什么那么长

    有朋友问,BERT等大模型的参数量怎么计算的?这个问题,李沐在BERT那篇论文中讲过,主要包括几部分。1、词嵌入:token数量乘以token表示的向量长度,就是 V H;2、注意力计算没有参数,只计算多头注意力的投影矩阵,三个输入的权重矩阵,每个矩阵参数= H (H/头数) 头数

    2024年04月25日
    浏览(33)
  • 不是说嵌入式是风口吗,那为什么工作还那么难找?

    最近确实有很多媒体、机构渲染嵌入式可以拿高薪 ,这在行业内也是事实,但前提是你有足够的竞争力,真的懂嵌入式。 时至今日,能做嵌入式程序开发的人其实相当常见,尤其是随着树莓派、Arduino等开发板的普及,甚至软件工程师也可以转向嵌入式开发。 然而,真正能够

    2024年02月12日
    浏览(32)
  • 为什么在马云成功前就有那么多影像留下来?

    马云创业的各个阶段, 都有意无意得到媒体的推波助澜 ,不光是影像,还留下了很多相关的文字报道。站在当时的角度,马云或许并不总是以一种成功人士的身份出现,但即便如此,他做事情也足够新潮、足够前卫、或者足够具有正能量。媒体对于类似的人或事,一向有着

    2024年02月01日
    浏览(40)
  • 云服务器那么安全稳定,为什么大厂还要自建机房

    一般来说选择自建机房或者是云服务商要考虑的几个问题 成本 安全性 管理 通常来说自建机房,需要自己考虑很多问题,比如 电费 网络 Raid 可靠性 安全性 还要计算运维的成本 似乎从哪个角度来说,自建机房都是不大划算的。 但是为什么还有一些公司要自建机房呢? 首先

    2023年04月08日
    浏览(38)
  • Windows 程序开机自启动速度优化,为什么腾讯会议自启动速度那么高?

    目录 一、问题的说明和定义 二、问题的分析 1.问题初步分析 2.详细的分析: 2.1Windows常见的自启动方式 2.2Windows常见的自启动方式的细节分析 三、问题的解决方案 1、为什么腾讯会议Rooms那么快 2.我们是否可以跟腾讯会议一样快 这两天有个优化项需要做个技术调研,就是我们

    2024年02月02日
    浏览(78)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包