并发编程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并发编程实战》

目录
相关文章
|
3月前
|
架构师 Java 数据库连接
Java异常处理的20个最佳实践:告别系统崩溃
你是否在为如何处理异常而困扰? 你是否曾被面试官问道Java异常处理的最佳实践有哪些? 本文汇总了Java异常处理的20个最佳实践:让你告别系统崩溃,面试游刃有余
979 2
Java异常处理的20个最佳实践:告别系统崩溃
|
3月前
|
存储 NoSQL 编译器
实战总结|抽丝剥茧,记一次神奇的崩溃
本文详细回放了一个崩溃案例的分析过程。回顾了C++多态和类内存布局、pc指针与芯片异常处理、内存屏障的相关知识。
|
1天前
|
安全 C# 开发者
【C# 多线程编程陷阱揭秘】:小心!那些让你的程序瞬间崩溃的多线程数据同步异常问题,看完这篇你就能轻松应对!
【8月更文挑战第18天】多线程编程对现代软件开发至关重要,特别是在追求高性能和响应性方面。然而,它也带来了数据同步异常等挑战。本文通过一个简单的计数器示例展示了当多个线程无序地访问共享资源时可能出现的问题,并介绍了如何使用 `lock` 语句来确保线程安全。此外,还提到了其他同步工具如 `Monitor` 和 `Semaphore`,帮助开发者实现更高效的数据同步策略,以达到既保证数据一致性又维持良好性能的目标。
7 0
|
30天前
|
SQL 缓存 安全
codereview开发问题之CodeReview阶段性能问题如何解决
codereview开发问题之CodeReview阶段性能问题如何解决
|
30天前
|
Java Spring 容器
codereview开发问题之在CodeReview异常处理时的问题如何解决
codereview开发问题之在CodeReview异常处理时的问题如何解决
|
安全
导致并发程序出现问题的根本原因是什么?
导致并发程序出现问题的根本原因是多线程之间的竞争条件和共享资源的访问冲突。多线程环境下,多个线程同时访问和修改共享资源时,可能会导致数据不一致、竞态条件、死锁等问题。
197 0
|
3月前
|
JSON 缓存 前端开发
编写代码前,如何规避尽可能多的前端bug?
编写代码前,如何规避尽可能多的前端bug?
41 0
|
存储 缓存 编译器
并发编程Bug的根源
并发编程Bug的根源
|
缓存 Java 编译器
并发编程笔记1:并发bug的成因
并发编程笔记1:并发bug的成因
|
缓存 算法 Java
Java并发编程Bug的源头
Java并发编程Bug的源头
95 0
Java并发编程Bug的源头