问题
线程安全的临界区需要依靠锁,而锁的获取必须也要保证自己是线程安全的,也就是说,不能出现两个线程同时得到锁的情况,那么锁是如何保证自己是线程安全的呢?或者说,在操作系统以及CPU层面,锁是如何实现的?
锁的本质
多个进程同时访问某公共变量要进行临界区的互斥访问,不然容易发生冲突,或者集群状态下为了保证各个节点的数据一致性也需要进行同步,实现同步一般都通过锁机制(包括信号量)来完成。但任何锁,追究到底层,都要由操作系统的原子操作来保证
实际上锁就是一个变量,通过这个变量的值来判断是不是已加锁,比如可以定义变量lock=1表示未被加锁,lock=0表示已加锁。那么加锁过程可以描述为:判断变量lock是否等于1,如果lock=1,则将lock改为0表示已加锁,然后进入临界区。这时别的线程也想进入临界区,一判断lock=0,那么将不被允许进入临界区,直到拥有锁的线程释放锁lock,即将lock改为1。
锁的实现
如果不加任何限制条件,我们可能会发现,这样的方式会造成一些冲突: 线程a想要进入临界区,判断lock=1,可以进入临界区,此时在另一个cup核执行的线程b也判断lock=1,也会进入临界区,这样两个线程都会访问临界变量,发生冲突。
锁是如何保证同一时刻只有一个线程(协程)访问临界区的,这涉及到锁的具体实现。 最简单的方法禁用中断就可以,而且要禁用所有cup核的中断。这就是解决思路,但是实际并不是这么操作的,因为屏蔽中断会导致系统效率低下 一般来说,锁的实现依赖底层的硬件指令,TAS(Test And Set)和 CAS(Compare And Set)是其中两个被广泛使用的硬件指令。 Test And Set TAS指令的语义是:向某个内存地址写入值1,并且返回这块内存地址存的原始值。TAS指令是原子的,这是由实现TAS指令的硬件保证的(这里的硬件可以是CPU,也可以是实现了TAS的其他硬件)。
同TAS一样,CAS也是由硬件支持的原子操作。在x86架构中,CAS对应的汇编指令是CMPXCHG。 CAS的语义是:比较某个内存地址的值与一个给定值(这个给定值是上一刻从此内存地址读出来的),如果相等,则把一个新值写入到此内存地址
这些硬件支持的指令会阻塞其他cpu核对相关内存的缓存块的访问,从而达到原子效果
|