golang 互斥锁原理

本文深入探讨了Golang中的互斥锁Mutex,包括其概念、数据结构、状态组成、加锁与解锁过程,以及自旋机制和饥饿模式。Mutex通过信号量控制协程的阻塞与唤醒,保证多线程访问资源的安全性。文章详细阐述了Lock和Unlock方法,特别是自旋过程,强调了自旋在特定条件下的优势及可能导致的问题。
摘要由CSDN通过智能技术生成

互斥锁

1、互斥锁概念

  • 对于同一份资源来说,为了保证 在多个线程或者协程同时访问的时候,保证数据的读取不会出现错误。
  • 互斥锁相对于 读写锁而言更加绝对一些,互斥锁不论是读写,加过锁后,都是互斥的,需要等待

2、Mutex 数据结构

type Mutex struct {
   
	state int32		// 互斥锁状态
	sema  uint32	// 信号量
}
  • sema 表示信号量,用来控制 goroutine 的 阻塞休眠和唤醒。协程阻塞时等待该信号量,协程解锁是释放该信号量并唤醒等待信号量的协程
  • state 表示 互斥锁的状态,0表示 未上锁,1表示上锁状态

3、state 状态组成

state 一共32位,最低三位依次是 Locked、Woken、Starving,剩下的其他位表示 等待锁的goroutine

在这里插入图片描述

  • Waiter 表示等待锁释放的goroutine数量
  • starving 表示当前锁释放处于饥饿状态,0 不饿 1 饥饿。饥饿状态说是有协程阻塞超过了1ms
  • woken 表示唤醒状态,0 没唤醒,1 唤醒状态。唤醒状态表示正在加锁
  • locked 表述是否锁定 0 未锁定 1 已锁定

协程之间抢占锁实际就是看谁能给locked 赋值。赋值为1说明抢占成功,抢不到的话就是0.
抢不到的话就等待 sema 的信号量,一旦有协程解锁,等待的协程就会被依次唤醒
woken 和 starving 主要用户控制协程之间的抢锁过程

4、Mutex 的方法

在这里插入图片描述

Mutex 对外仅暴露两个方法,lock 和 unlock。加红锁的是私有的方法

Lock

加锁过程
1、无阻塞加锁图解

在这里插入图片描述

当前锁是空闲状态,且没有协程抢占。
加锁前判断Locked 是否是 0 , 如果是0 的话 改为 1 ,加锁成功。
仅 Locked 状态位变为1 ,其他位置不变化

2、阻塞加锁图解

在这里插入图片描述

协程B加锁的时候,发现该锁已经被协程A占用了
此时 Waiter 计数器会增加1,协程B 被阻塞,知道协程A释放锁。Locked 变为0 ,协程B才会被唤醒

加锁代码
// Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
func (m *Mutex) Lock() {
   
	// Fast path: grab unlocked mutex.
	if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
   
		if race.Enabled {
   
			race.Acquire(unsafe.Pointer(m))
		}
		return
	}
	// Slow path (outlined so that the fast path can be inlined)
	m.lockSlow()
}
  • 首先使用 CAS 方法(CompareAndSwapInt32)判断是否可以获取资源。如果可以获取资源的话,就直接修改 Locked 的值,如果获取不到的话就 执行 lockSlow() 方法,自旋抢锁,直到拿到锁
1、CompareAndSwapInt32 方法介绍
  • CAS 是原子操作的一种,是在包 sync.atomic 下面

  • 原子操作即是进行过程中不能被中断的操作。针对某个值的原子操作在被进行的过程中,CPU绝不会再去进行其他的针对该值的操作。
    为了实现这样的严谨性,原子操作仅会由一个独立的CPU指令代表和完成
    在这里插入图片描述

  • 对应的其他方法还有 增或减(add)、比较并交换(CompareAndSwap)、载入(Load)、存储(Store)、交换(Swap)

  • 对应的操作类型 :int32,int64,uint32,uint64,uintptr,unsafe.Pointer

我们互斥锁这里用到的是
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
1、调用函数后,先判断 addr 指针指向的值和传入的 old 是否相同
2、如果相同,会用传入的 new 对addr的值进行替换。反之不替换

2、lockSlow 方法

加锁失败后就会进入 lockSlow 方法,自旋抢锁

func (m *Mutex) lockSlow() {
   
	v
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值