平时在学习中遇到的一些并发相关的概念, 做个笔记整理一下。
1 相关概念
1.1 同步 VS 异步
同步和异步关注的是 消息通信机制
。
同步: 调用方发起一个调用, 在没有得到结果之前, 这个调用就不返回, 即调用方主动等待调用的结果。
(A 调用了一个方法, A 必须等到这个方法返回结果了, 才能执行后续的代码)
异步: 调用方发起一个调用后, 就不管了, 后续有结果, 可以让被调用方通知调用方 (A 调用了一个方法后, 就继续执行后面的代码, 不用等待方法的返回值)。
一般情况下, 被调用方处理完后, 会通过状态、通知、回调函数等来通知调用者。
1.2 阻塞 VS 非阻塞
阻塞和非阻塞关注的是 程序在等待调用结果时的状态
。
阻塞: 调用方发起一个调用, 在没有得到结果之前, 调用方会被挂起, 无法进行其他的操作, 等待执行结果。
非阻塞: 调用方发起一个调用, 在没有得到结果之前, 调用方不会被挂起。
1.3 并发 VS 并行
并发: 多个任务交替执行, 也就是同一个时刻只有一个任务在执行。
并行: 多个任务同时执行, 也就是同一个时刻有多个任务在执行。
1.4 进程 VS 线程 VS 协程
进程是程序向操作系统申请资源 (如内存空间和文件句柄) 的基本单位。也可以理解为是线程的一个容器。
线程也叫轻量级进程, 是程序执行的最小单位, 本身只拥有少部分执行必须的资源。是进程中可独立运行的最小单位。
协程也叫微线程, 用户可以自己控制协程切换的时机, 不再需要陷入系统的内核态。和线程的区别: 线程间的切换都是由系统进行的, 但是协程可以由代码显示进行调度。
1.5 用户态 VS 内核态
用户态和内核态是操作系统的两种运行级别, 两者最大的区别就是特权级不同。 运行在用户态的程序不能直接访问操作系统内核数据结构和程序, 而运行在内核态的程序则没有限制。
Linux 的架构中, 很重要的一个能力就是操纵系统资源的能力。但是系统资源是有限的, 无限制的使用, 将会导致浪费, 同时错误地使用会导致错误。
为了减少资源浪费, Linux 制定了一个等级制定, 即特权。Linux 将特权分成两个层次, 以 0 和 3 标识, 0 的特权级要高于 3。 0 特权级在操纵系统资源上是没有任何限制的, 可以执行任何操作, 而 3 特权则会受到极大的限制。
特权级 0 称之为内核态, 特权级 3 称之为用户态。
内核态和用户态之间的转换方式有 3 种
- 系统调用: 如调用 write(), read(), send() 等 IO 函数等操作, 进程需要会进入内核态使用内核代码去完成操作。系统调用本身就是中断, 软件中断, 和硬件中断不同
- 异常: 当前进程运行在用户态, 如果这个时候发生了异常事件, 就会触发切换
- 外设中断: 当外设完成用户的请求时, 会向 CPU 发送中断信号
1.5.1 用户空间 VS 内核空间
操作系统将虚拟内存划分为两部分:
一部分是用户空间, 供各个进程使用, 在这部分空间内各个进程之间相互独立。
另一部分是内核空间, 只提供给供内核使用, 每个进程可以通过系统调用进入内核, 所以内核空间由系统内的所有进程共享。
在 Linux 系统中, 用户空间和内核空间所占的虚拟内存比例是 3:1, 用户空间占了内存的低位, 内核空间占据内存的高位。
可以简单的理解为: 进程工作在用户空间就是用户态, 进程工作在内核空间就是内核态。
1.5.2 零拷贝
传统的磁盘读取
- 应用程序发起系统调用, 进入内核态
- 从磁盘中读取数据到内核空间
- 将数据从内核空间拷贝到用户空间
- 应用程序恢复为用户态
零拷贝的实现
- 应用程序发起系统调用, 进入内核态
- 从磁盘读取数据到内核空间, 再将内核空间的数据映射到用户空间, 内核空间和用户空间不需要拷贝了
- 应用程序恢复为用户态
2 独占锁和共享锁
2.1 定义
独占锁: 锁同一时刻只能被一个线程持有。
共享锁: 锁同时可以被多个线程持有。
2.2 具体的实现
读锁是共享锁的具体实现, 而写锁是独占锁的具体实现。
读锁可以在没有写锁的时候被多个线程同时持有, 而写锁是独占的, 写锁的优先级要高于读锁。
2.3 锁的附加属性
2.3.1 公平性
锁的获取是否是公平的, 即锁的获取是否按照申请是顺序获取。
如果像排队一样, 先来的先获取锁, 后来的后获取锁, 具备公平性, 这就是公平锁。
如果锁的获取是没有规则的, 可能后到的线程先获取锁, 这就是不公平锁。
2.3.2 可重入性
同一个线程在已经获取到一个锁了, 后续在未释放的情况下又一次申请同一个锁
- 申请成功, 锁具备可重入性
- 申请失败, 锁不具备可重入性
2.4 锁引起的问题
2.4.1 死锁
两个或者更多的线程相互占用对方的资源的锁, 而又相互等对方释放锁而被永远暂停 (线程的生命周期状态为 BLOCKED或者 WAITING)。
2.4.2 活锁
线程拿到资源却又相互释放不执行, 相互谦让, 都主动将资源释放给别的线程使用, 这样这个资源在多个线程之间跳动而又得不到执行。
2.4.3 线程饥饿
线程一直无法获得其所需的资源而导致其任务一直无法进展 (优先级高的线程一直抢占优先级低线程的资源, 导致低优先级线程无法得到执行)。
3 并发的优缺点
3.1 优点
- 充分利用多核 CPU 的运算能力, 使线程之间可以并行操作, 更快的处理能力
- 异步执行比同步执行更快, 使程序的处理能力得到提升
- 资源利用率提高, 如文件读取等情况
3.2 缺点
1. 频繁的上下文切换
在操作系统内部, 多线程直接的实现是基于时间片实现的。 CPU 基于时间片不断的切换线程, 使得少量的 CPU 能够支撑起大量的线程。但是每次切换线程为了能够
在下次轮到这个线程的时间片到了能够继续执行下去, 需要把当前的状态保存起来, 下次好恢复。而这个过程是很耗时间的, 所以大量的线程存在, 会导致更高的上下
文切换, 反而会影响到系统的效率。
2. 数据准确性问题
多个线程共享数据时可能会产生于期望不相符的结果
3. 线程活性问题
死锁, 活锁, 线程饥饿等问题
4 参考
浅谈用户态和内核态以及用户空间和内核空间
并发编程基础概念
原来 8 张图, 就可以搞懂「零拷贝」了
NIO效率高的原理之零拷贝与直接内存映射