各种锁
·
giftia
GO 锁
互斥锁 sync.Mutex
互斥锁是最基本的锁,它可以保证同一时刻只有一个线程可以访问共享资源。
// noCopy 是一个空结构体,用于防止 Mutex 被拷贝。
type noCopy struct{}
type isync.Mutex struct {
state int32 // 锁的状态,使用原子操作管理。0 表示未加锁,1 表示已加锁。
sema uint32 // 信号量,用于实现锁的等待队列。
}
type Mutex struct {
_ noCopy
mu isync.Mutex
}
读写锁 sync.RWMutex
读读共享,读写互斥,写写互斥。
type RWMutex struct {
w Mutex // 互斥锁,用于控制写操作
writerSem uint32 // 写操作的信号量
readerSem uint32 // 读操作的信号量
readerCount atomic.Int32 // 当前正在持有读锁的读操作数量
readerWait atomic.Int32 // 当前正在等待释放的读操作数量
}
注意:
- 同一个协程不能连续多次调用 Lock, 否则发生死锁
- 锁资源时尽量缩小资源的范围,以免引起其它协程超长时间等待
- mutex 传递给外部的时候需要传指针,不然就是实例的拷贝,会引起锁失败
- 善用 defer 确保在函数内释放了锁
- 使用 - race 在运行时检测数据竞争问题,go test -race ….,go build -race ….
- 善用静态工具检查锁的使用问题
- 使用 go-deadlock 检测死锁,和指定锁超时的等待问题
- 能用 channel 的场景别使用成了 lock
MySQL 锁
表锁
表级锁会对整张表进行锁定。
LOCK TABLES table_name [READ|WRITE];
UNLOCK TABLES;
行锁
行级锁仅对需要操作的行进行锁定。
SELECT ... WHERE ... FOR UPDATE; -- 排他锁
SELECT ... WHERE ... LOCK IN SHARE MODE; -- 共享锁
共享锁与排他锁
共享锁也被称为读锁,多个事务可以同时对同一资源加共享锁。
排他锁又称为写锁,同一时间只允许一个事务对资源加排他锁。
间隙锁
间隙锁用于锁定索引记录之间的间隙,防止幻读。
Redis 锁
分布式锁
Redis 实现分布式锁,使用 SETNX 命令,通过给一个 key 设置一个值,来实现锁。
SETNX key value
SET key value NX EX timeout
推荐2,原子性
分布式锁的优化
- 设置过期时间,防止锁一直被占用
- 看门狗,定期检查主线程是否存活,给锁续期
- 加uuid,防止锁重入
其他锁
乐观锁
假设并发冲突很少发生,因此不加锁,只在更新数据时检测是否有冲突。
悲观锁
假设并发冲突一定会发生,因此在操作数据前先加锁,防止其他线程修改。
自旋锁
在加锁失败时,不立即阻塞,而是尝试自旋,直到成功为止。(代码中即用for死循环实现)
- 适用场景:锁的时间短,使用自旋的时间小于线程切换的时间。