多线程问题的本质:有序性,可见性,原子性
我们处理线程安全可以有几个层次:
- 能否做成无状态的不变对象。无状态是最安全的。
- 能否线程封闭
(1)栈封闭,多采用局部变量
(2)线程局部存储(用空间换性能)
(3)程序控制线程封闭(Hash,将同一hash val的的请求丢给同一个线程去处理) - 采用何种同步技术
多线程同步的方式
线程同步
多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争,比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就是在一个线程进行了规定操作后,就进入等待状态, 等待其他线程执行完他们的指定代码过后 再将其唤醒;
(在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。)
(1) 信号量 semphore
(2) 共享内存 shared_memory
(3) 读写锁 rw_lock
(4) 条件变量 condition
线程间存在依赖
(5) 互斥量 mutex
互斥锁
对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。
- 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
- 互斥量:为协调共同对一个共享资源的单独访问而设计的。
- 信号量:为控制一个具有有限数量用户资源而设计。
- 读写锁
自旋锁
但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁。如果等待的时间比较短,适合使用自旋锁,占用大量的CPU资源
锁的实现机制
在硬件层面,CPU提供了原子操作、关中断、锁内存总线的机制。
禁中断
既然只有中断才能把上锁过程打断,造成多线程操作失败。我先关中断不就得了,在加锁操作完成后再开中断。
普通的原子指令
上面这个手段太笨重了,能不能硬件做一种加锁的原子操作呢?能,大名鼎鼎的“test and set”指令就是做这个事情的。
锁内存总线 + 原子指令
通过上面的手段,单核环境下,锁的实现问题得到了圆满的解决。
那么多核环境呢?简单嘛,还是“test and set”不就得了,这是一条指令,原子的,不会有问题的。
真的吗,单独一条指令能够保证该指令在单个核上执行过程中不会被中断打断,但是两个核同时执行这个指令呢?。。。
我再想想,硬件执行时还是得从内存中读取lock,判断并设置状态到内存,貌似这个过程也不是那么原子嘛。对,多个核执行确实会存在这个问题。怎么办呢?首先我们得明白这个地方的关键点,关键点是两个核会并行操作内存而且从操作内存这个调度来看“test and set”不是原子的,需要先读内存然后再写内存,如果我们保证这个内存操作是原子的,就能保证锁的正确性了。
确实,硬件提供了锁内存总线的机制,我们在锁内存总线的状态下执行test and set操作,就能保证同时只有一个核来test and set,从而避免了多核下发生的问题。
无锁
- 无锁算法的底层实现 — CAS
- 借助内存访问WORD的原子性
参考:
https://xie.infoq.cn/article/c7045d48ccb28872277445ff8
http://jm.taobao.org/2011/12/07/1347/
https://blog.csdn.net/heiyeshuwu/article/details/9722443
https://www.cnblogs.com/jing99/p/11984966.html
https://www.qbitai.com/2019/12/9895.html
https://www.jianshu.com/p/d585b3938dea