giftiaのblog

各种锁

· 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 // 当前正在等待释放的读操作数量
}

注意:

  1. 同一个协程不能连续多次调用 Lock, 否则发生死锁
  2. 锁资源时尽量缩小资源的范围,以免引起其它协程超长时间等待
  3. mutex 传递给外部的时候需要传指针,不然就是实例的拷贝,会引起锁失败
  4. 善用 defer 确保在函数内释放了锁
  5. 使用 - race 在运行时检测数据竞争问题,go test -race ….,go build -race ….
  6. 善用静态工具检查锁的使用问题
  7. 使用 go-deadlock 检测死锁,和指定锁超时的等待问题
  8. 能用 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,原子性

分布式锁的优化

  1. 设置过期时间,防止锁一直被占用
  2. 看门狗,定期检查主线程是否存活,给锁续期
  3. 加uuid,防止锁重入

其他锁

乐观锁

假设并发冲突很少发生,因此不加锁,只在更新数据时检测是否有冲突。

悲观锁

假设并发冲突一定会发生,因此在操作数据前先加锁,防止其他线程修改。

自旋锁

在加锁失败时,不立即阻塞,而是尝试自旋,直到成功为止。(代码中即用for死循环实现)

  • 适用场景:锁的时间短,使用自旋的时间小于线程切换的时间。