在学习了为什么要学习并发编程和如何学习并发编程后,我们再来看看并发编程需要解决的核心问题。
如果你细心观察的话,你会发现,不管是哪一门编程语言,并发类的知识都是在高级篇里。换句话说,这块知识点其实对于程序员来说,是比较进阶的知识。
你我都知道,编写正确的并发程序是一件极困难的事情,并发程序的Bug往往会诡异地出现,然后又诡异地消失,很难重现,也很难追踪,很多时候都让人很抓狂。但要快速而又精准地解决“并发”类的疑难杂症,你就要理解这件事情的本质,追本溯源,深入分析这些Bug的源头在哪里。
并发程序幕后的故事
这些年,我们的CPU、内存、I/O设备都在不断迭代,不断朝着更快的方向努力。但是,在这个快速发展的过程中,有一个核心矛盾一直存在,就是这三者的速度差异。
CPU和内存的速度差异可以形象地描述为:CPU是天上一天,内存是地上一年(假设CPU执行一条普通指令需要一天,那么CPU读写内存得等待一年的时间)。
内存和I/O设备的速度差异就更大了,内存是天上一天,I/O设备是地上十年。
程序里大部分语句都要访问内存,有些还要访问I/O,根据木桶理论(一只水桶能装多少水取决于它最短的那块木板),程序整体的性能取决于最慢的操作——读写I/O设备,也就是说单方面提高CPU性能是无效的。
为了合理利用CPU的高性能,平衡这三者的速度差异,计算机体系机构、操作系统、编译程序都做出了贡献,主要体现为:
- CPU增加了缓存,以均衡与内存的速度差异;
- 操作系统增加了进程、线程,以分时复用CPU,进而均衡CPU与I/O设备的速度差异;
- 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。
现在我们几乎所有的程序都默默地享受着这些成果,但是天下没有免费的午餐,并发程序很多诡异问题的根源也在这里。
并发编程需要解决三个核心问题
- 可见性问题
- 原子性问题
- 有序性问题
再总结下一些基本知识供大家参考
可见性问题
对于可见性那个例子我们先看下定义:
可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
并发问题往往都是综合证,这里即使是单核CPU,只要出现线程切换就会有原子性问题。
或许我们可以把线程对变量的读可写都看作时原子操作,也就是cpu对变量的操作中间状态不可见,这样就能更加理解什么是可见性了。
CPU缓存刷新到内存的时机
cpu将缓存写入内存的时机是不确定的。除非你调用cpu相关指令强刷。
指令优化
除了编译优化,有一部分可以通过看汇编代码来看,但是CPU和解释器在运行期也会做一部分优化,所以很多时候都是看不到的,也很难重现。
IO操作
io操作不占用cpu,读文件,是设备驱动干的事,cpu只管发命令(也即执行驱动程序)。发完命令,就可以干别的事情了。
寄存器切换
寄存器是共用的,A线程切换到B线程的时候,寄存器会把操作A的相关内容会保存到内存里,切换回来的时候,会从内存把内容加载到寄存器。可以理解为每个线程有自己的寄存器