March 13, 2017
并发问题
1. 线程安全要素
#
要素 |
说明 |
引入原因 |
可见性 |
一个线程对共享变量的修改,另外一个线程能够立刻看到 |
CPU 缓存 |
原子性 |
一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行 |
分时复用 |
有序性 |
程序执行的顺序按照代码的先后顺序执行 |
编译重排序,指令重排序 |
理想情况下 CPU 应该使用 MESI 协议来保证不同 CPU 之间的缓存一致性,实际上,为了保证 cpu 的效率,AMD 用 MOESI 协议,Intel 用 MESIF 协议来保证缓存一致性,代价就是无法真正的保证缓存强一致性,需要软件使用 memory barrier(内存屏障)或者 lock 指令前缀等来保证变量在不同 cpu cache 中的强一致性。
2. 线程安全分类
#
线程安全不是一个非真即假的命题,可以将共享数据按照安全程度的强弱顺序分成以下五类:不可变
、绝对线程安全
、相对线程安全
、线程兼容
和线程对立
。
安全程度 |
说明 |
不可变 |
不可变的对象一定是线程安全的,不需要再采取任何的线程安全保障措施 |
绝对线程安全 |
不管运行时环境如何,调用者都不需要任何额外的同步措施 |
相对线程安全 |
需要保证对这个对象单独的操作是线程安全的,在调用的时候不需要做额外的保障措施。但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。 |
线程兼容 |
线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用 |
线程对立 |
线程对立是指无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码 |
3. 线程安全的实现方法
#
实现方法 |
说明 |
阻塞同步 |
也称为互斥同步,无论共享数据是否真的会出现竞争,它都要进行加锁 |
非阻塞同步 |
先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施,比如 CAS |
无同步方案 |
如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性,比如栈封闭,线程本地存储,可重入代码等 |
4. 语言相关的解决方案
#
4.1 Java 解决方案
#
要素 |
解决方案 |
可见性 |
基本数据类型读取和赋值;synchronized;Lock;volatile |
原子性 |
synchronized;Lock; Atomic |
有序性 |
synchronized;Lock;volatile + Happen Before 原则 |
4.2 C++ 解决方案
#
要素 |
解决方案 |
可见性 |
mutex;lock_guard;unique_lock;atomic + memory_order |
原子性 |
mutex;lock_guard;unique_lock;atomic |
有序性 |
mutex;lock_guard;unique_lock;atomic + memory_order |