在多线程中,为了保证数据操作的一致性,操作系统引入了锁机制,用于保证临界区代码的安全。通过锁机制,能够保证在多核多线程环境中,在某一个时间点上,只能有一个线程进入临界区代码,从而保证临界区中操作系统的一致性,
所谓的锁说白了就是内存中的一个整型数,拥有两种状态:空闲状态和上锁状态
看锁无非就是这几句
1、read lock;
2、判断lock状态;
3、如果已经加锁,失败返回;
4、把锁状态设置为上锁;
5、返回成功。
那什么时候会导致两个线程同时以获得锁呢
1、中断:假设线程A执行完第一步,发生中断,中断返回后,OS调度线程B,线程B也来加锁并且加锁成功,这时OS调度线程A执行,线程从第二步开始执行,也加锁成功。
2、多核:当然了,想想上面举的例子,描述的就是两个核同时获取到锁的情况。
解决方法1只有中断可以把上锁过程打断,造成多线程操作失败,我先关中断就好了,在加锁操作完成后再开中断,但是这样太笨了,
test and set 指令就是用来做一种加锁的原子操作,只要让不被中断就好了
在多核状态下,我们在锁内存总线的状态下执行test and set操作,就能保证同时只有一个核来test and set,从而避免了多核下发生的问题。
go面试题:锁的实现原理sync-mutex篇
Go面试题(四):锁的实现原理sync-mutex篇 - 掘金 (juejin.cn)
在Go中对于并发程序进行公共资源的访问的限制最常用的就是互斥锁(sync.mutex)的方式
sync.mutex的常用方法有两个:
- Mutex.lock()用来获取锁
- Mutex.Unlock()用于释放锁
在 Lock 和 Unlock 方法之间的代码段称为资源的临界区,这一区间的代码是严格被锁保护的,是线程安全的,任何一个时间点最多只能有一个goroutine在执行。
互斥锁中有两个变量 state和sema
互斥锁state主要记录了四种状态
1当前等待这个锁的数量
2当前锁是否处于饥饿状态
3当前锁是否有goroutine已被唤醒
4当前锁是否被goroutine唤醒
sema信号量的作用:当持有锁的gorouine释放锁,会释放sema信号量,这个信号量会唤醒之前抢锁阻塞的gorouine来获取锁
锁的两种模式,正常模式和饥饿模式
注意事项:
‘1在一个goroutine中执行Lock()加锁成功后,不要重复加锁,否则会panic
2在Lock()之前执行Unlock()释放锁 会panic
3对于同一把锁,可以在一个goroutine中执行Lock加锁成功后,可以在另外一个gorouine中执行Unlock释放这把锁