并发编程Bug源头

简介: 并发编程Bug源头

一.简介

编写正确的并发程序是一件困难的事情,往往调试过程中发生很多不确定的事情,这时需要对理论知识的一个认知,能够准确的追踪问题。

二.硬件背景

CPU,内存,I/O设备不断迭代,这三者速度存在差异,CPU和内存的速度差异可以形象的描述:CPU速度最快,内存次之,I/O设备更次之。

为了合理利用CPU的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:

  • CPU增加缓存,以均衡与内存速度差异;
  • 操作系统增加了进程、线程,以分时复用CPU,进而均衡CPU与I/O设备的速度差异;
  • 编译程序优化指令执行次序,使得缓存能够更合理利用。

三.源头

3.1 源头一

缓存导致的可见性问题

在单核时代,所有的线程都是在一颗 CPU 上执行,CPU 缓存与内存的数据一致性容易解决,所有都是串行。

一个线程对共享变量的修改,另一个线程能够立刻看到,我们称为可见性。

image.png

多核时代,每颗 CPU 都有自己的缓存,这时 CPU 缓存与内存的数据一致性就没那么容易解决了,当多个线程在不同的 CPU 上执行时,这些线程操作的是不同的 CPU 缓存。

3.2 源头二

线程切换带来的原子性问题

由于 IO 太慢,早期的操作系统就发明了多进程,即便在单核的 CPU 上我们也可以一边听着歌,一边写 Bug,这个就是多进程的功劳。

操作系统允许某个进程执行一小段时间,例如 50 毫秒,过了 50 毫秒操作系统就会重新选择一个进程来执行(我们称为“任务切换”),这个 50 毫秒称为“时间片”。

image.png

我们把一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性。CPU 能保证的原子操作是 CPU 指令级别的,而不是高级语言的操作符,这是违背我们直觉的地方。因此,很多时候我们需要在高级语言层面保证操作的原子性。

我们把一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性。

“原子性”的本质是什么?其实不是不可分割,不可分割只是外在表现,其本质是多个资源间有一致性的要求,操作的中间状态对外不可见。

3.3 源头三

编译优化带来的有序性问题

那并发编程里还有没有其他有违直觉容易导致诡异 Bug 的技术呢?有的,就是有序性。顾名思义,有序性指的是程序按照代码的先后顺序执行。编译器为了优化性能,有时候会改变程序中语句的先后顺序,例如程序中:“a=6;b=7;”编译器优化后可能变成“b=7;a=6;”,在这个例子中,编译器调整了语句的顺序,但是不影响程序的最终结果。不过有时候编译器及解释器的优化可能导致意想不到的 Bug。

四.总结

在介绍可见性、原子性、有序性的时候,特意提到缓存导致的可见性问题,线程切换带来的原子性问题,编译优化带来的有序性问题,其实缓存、线程、编译优化的目的和我们写并发程序的目的是相同的,都是提高程序性能。

参考

《Java并发编程实战》

目录
相关文章
|
8月前
|
架构师 Java 数据库连接
Java异常处理的20个最佳实践:告别系统崩溃
你是否在为如何处理异常而困扰? 你是否曾被面试官问道Java异常处理的最佳实践有哪些? 本文汇总了Java异常处理的20个最佳实践:让你告别系统崩溃,面试游刃有余
1262 2
Java异常处理的20个最佳实践:告别系统崩溃
|
15天前
|
开发者 UED
开发者应该如何避免“效率陷阱”
在开发工作中,常见的两种“效率陷阱”是“以为效率高”和“以为进度快”。前者指为快速上线而忽视代码质量,导致后期维护困难和技术债务增加;后者则是通过延长工时来赶进度,反而影响开发人员状态和项目质量。避免这两种陷阱的关键在于制定合理的工作计划,确保任务进度与质量并重,保持开发的可持续性。
|
8月前
|
算法 Java
Java多线程基础-13:一文阐明死锁的成因及解决方案
死锁是指多个线程相互等待对方释放资源而造成的一种僵局,导致程序无法正常结束。发生死锁需满足四个条件:互斥、请求与保持、不可抢占和循环等待。避免死锁的方法包括设定加锁顺序、使用银行家算法、设置超时机制、检测与恢复死锁以及减少共享资源。面试中可能会问及死锁的概念、避免策略以及实际经验。
137 1
|
安全
导致并发程序出现问题的根本原因是什么?
导致并发程序出现问题的根本原因是多线程之间的竞争条件和共享资源的访问冲突。多线程环境下,多个线程同时访问和修改共享资源时,可能会导致数据不一致、竞态条件、死锁等问题。
272 0
|
8月前
|
算法
出现线程死锁缺陷一般有那些原因?该怎么解决?
出现线程死锁缺陷一般有那些原因?该怎么解决?
92 1
|
安全 算法 关系型数据库
如何避免在C#中出现混乱代码
如何避免在C#中出现混乱代码
|
存储 缓存 编译器
并发编程Bug的根源
并发编程Bug的根源
101 0
|
缓存 Java 编译器
并发编程笔记1:并发bug的成因
并发编程笔记1:并发bug的成因
115 0

热门文章

最新文章