最近面试关于多线程加锁的一些问题总结于此:
在多线程编程中,为了保证数据操作的一致性,操作系统引入了锁机制,用于保证临界区代码的安全。通过锁机制,能够保证在多核多线程环境中,在某一个时间点上,只能有一个线程进入临界区代码,从而保证临界区中操作数据的一致性。
所谓的锁,无非就是内存中的一个整数,拥有两种状态:空闲状态和上锁状态.
那么OS如何保证锁操作本身的原子性的呢?
为了弄明白锁的实现原理,我们首先看看如果OS不采用任何其他手段,什么情况下会导致上锁失败?假如我们把加锁过程用如下伪码表示:
1、read lock;
2、判断lock状态;
3、如果已经加锁,失败返回;
4、把锁状态设置为上锁;
5、返回成功。
明白汇编的同学一看就明白上述每一步都能对应到一条汇编语句,所以我们可以认为每一步本身是原子的。
那么什么情况下会导致两个线程同时获取到锁呢?
1 中断:假设线程A执行完第一步,发生中断,中断返回后,OS调度线程B,线程B也来加锁并且加锁成功,这时OS调度线程A执行,线程从第二步开始执行,也加锁成功。
2 多核:因为多线程共享存储,有可能同时访问这个变量.
明白了失败原因,解决手段也就明确了.
先看单核场景:
1 关中断,加锁完成之后再开中断
2 关中断有可能效率不高,硬件中有一种指令叫做 test and set是原子操作可以用来实现锁.
那么对于多核环境下呢?
关键点在于两个核会并行操作内存而且从操作内存这个角度来看"test and set"不是原子性的,需要先读内存然后再写内存,如果能够保证这个内存操作是原子的,就能保证锁的正确性.硬件提供了锁内存总线的机制,我们在锁内存总线的状态下执行test and set操作,就能保证同时只有一个核来test and set.
总结一下,在硬件层面,CPU提供了原子操作、关中断、锁内存总线的机制;OS基于这几个CPU硬件机制,就能够实现锁;再基于锁,就能够实现各种各样的同步机制(信号量、消息、Barrier等等等等)。所以要想理解OS的各种同步手段,首先需要理解本文介绍的内容,这时最原点的机制,所有的OS上层同步手段都基于此。
一. 何谓"原子操作":
原子操作就是: 不可中断的一个或者一系列操作, 也就是不会被线程调度机制打断的操作, 运行期间不会有任何的上下文切换(context switch).
二. 为什么关注原子操作?
1. 如果确定某个操作是原子的, 就不用为了去保护这个操作而加上会耗费昂贵性能开销的锁. - (巧妙的利用原子操作和实现无锁编程)
2. 借助原子操作可以实现互斥锁(mutex). (linux中的mutex_lock_t)
3. 借助互斥锁, 可以实现让更多的操作变成原子操作.
三. 单核CPU的原子操作:
在单核CPU中, 能够在一个指令中完成的操作都可以看作为原子操作, 因为中断只发生在指令间.
四. 多核CPU的原子操作: