Golang 底层原理剖析笔记-锁

本文详细阐述了Golang中的互斥锁实现、加锁/解锁流程,以及自旋、饥饿状态、公平性和读写锁的使用。特别强调了锁的公平性和可见性/原子性的处理方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

互斥锁

不支持可重入
饥饿状态是公平的,非饥饿是非公平的

结构

// A Mutex is a mutual exclusion lock.
// The zero value for a Mutex is an unlocked mutex.
//
// A Mutex must not be copied after first use.
//
// In the terminology of the Go memory model,
// the n'th call to Unlock “synchronizes before” the m'th call to Lock
// for any n < m.
// A successful call to TryLock is equivalent to a call to Lock.
// A failed call to TryLock does not establish any “synchronizes before”
// relation at all.
type Mutex struct {
	state int32 // 等待数量:是否饥饿:正常状态:锁定状态
	sema  uint32
}

state : 等待数量:是否饥饿:正常状态:锁定状态

饥饿状态:锁竞争较大。unlock 会唤醒最先申请加速的协程。lock 的结束阶段也会检查,如果是在饥饿状态下被唤醒的,判断是否需要退出饥饿状态。
正常状态:无锁状态
锁定状态

mutexWoken :在自旋的时候可以看到如下注释,起到一个锁预定的作用,通知解锁的时候不要唤醒其它协程。

// Active spinning makes sense.
			// Try to set mutexWoken flag to inform Unlock
			// to not wake other blocked goroutines.
			if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
				atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
				awoke = true
			}

加锁

锁级别:golang 的锁,系统锁。
共四个阶段:
1. 快速抢占( cas 改变 state 状态)
2. 自旋,只有锁是正常状态下才可以进入自旋
3. 信号量同步
4. 等待状态

在这里插入图片描述
自旋结束后还设置不了,就进入饥饿状态。

解锁

  1. 仅锁定状态,尝试快速的释放锁;
  2. 如果是饥饿状态,去全局哈希表查找对应的等待协程,FIFO 顺序唤醒;
  3. 如果是非饥饿状态且已经设置了预唤醒状态,直接退出,如果唤醒了等待队列中的线程,则将该线程放入 P 的 runext字段。

在这里插入图片描述

读写锁

引用:https://baijiahao.baidu.com/s?id=1653424247811116311&wfr=spider&for=pc
在这里插入图片描述
使用锁 + 信号量来实现,主要通过计数逻辑来对读写锁进行排队管理:读的时候判断读数量是否小于 0 , 小于 0 表示有写锁在等待。
读操作先原子操作计数,主要是判断是否为最后一个读者,是否存在读者,最后一个读者来释放锁。
写锁

读锁

readerCount :读锁的计数器
readerWaite :等待读锁释放的数量

readerCount - 1 <0 ,表示读锁全部释放
readerCount + 1 < 0 ,表示有写请求等待,读请求在信号量 readerSem 上进行等待。
readerWaite - 1 == 0 ,表示为最后一个读请求结束,唤醒写锁 : runtime_Semrelease(&rw.writerSem, false, 1)

加锁流程:
引用自:https://baijiahao.baidu.com/s?id=1653424247811116311&wfr=spider&for=pc
在这里插入图片描述

解锁流程:
引用自:https://baijiahao.baidu.com/s?id=1653424247811116311&wfr=spider&for=pc
在这里插入图片描述

写锁

写锁之间是严格互斥的,需要先获取 mutex;
修改 readerCount 数量,
检查 readerWait 数量, == 0 ,表示没有竞争.

// Wait for active readers.等待读请求通知
    if r != 0 && rw.readerWait.Add(r) != 0 {
        runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
    }
// 先进行抢占,关门,然后回复到原来的值,期间可能有读锁进入了,需要更新 readerWait ,查看新的值
r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders

加锁流程:
引用自:https://baijiahao.baidu.com/s?id=1653424247811116311&wfr=spider&for=pc
在这里插入图片描述

解锁流程:
引用自:https://baijiahao.baidu.com/s?id=1653424247811116311&wfr=spider&for=pc
在这里插入图片描述

全局锁信息 Semtable

在全局锁信息哈希表中通过 treap tree 的方式组织冲突。每个桶子有一个系统级别的锁。
在这里插入图片描述

// Asynchronous semaphore for sync.Mutex.

// A semaRoot holds a balanced tree of sudog with distinct addresses (s.elem).
// Each of those sudog may in turn point (through s.waitlink) to a list
// of other sudogs waiting on the same address.
// The operations on the inner lists of sudogs with the same address
// are all O(1). The scanning of the top-level semaRoot list is O(log n),
// where n is the number of distinct addresses with goroutines blocked
// on them that hash to the given semaRoot.
// See golang.org/issue/17953 for a program that worked badly
// before we introduced the second level of list, and
// BenchmarkSemTable/OneAddrCollision/* for a benchmark that exercises this.
type semaRoot struct {
	lock  mutex
	treap *sudog        // root of balanced tree of unique waiters.
	nwait atomic.Uint32 // Number of waiters. Read w/o the lock.
}

var semtable semTable

// Prime to not correlate with any user patterns.
const semTabSize = 251

type semTable [semTabSize]struct {
	root semaRoot
	pad  [cpu.CacheLinePadSize - unsafe.Sizeof(semaRoot{})]byte
}

问题

自旋条件

  1. 自选状态不会进行,如果:
    a.单核 cpu
    b. p 数量小于等于 1
    c. p 的本地队列上有其它待运行协程
    d. 自旋次数超过了设定的值

饥饿问题

  1. 当一个goroutine被唤醒时,如果其没有获取到锁,那么它将会被插入到等待队列的头部,而不是尾部。
  2. 如果其没有获取到锁,并且等待时间超过了1ms,那么mutex将会变为饥饿模式。

写锁公平性

加写锁的时候必须先进行mutex的加锁,而mutex本身在普通模式下是非公平的(抢占式的),只有在饥饿模式下才是公平的(FIFO)

可见性和原子性问题

通过LOCK指令配合CPU的MESI协议,实现可见性和内存屏障,同时通过 XADDL 则用来保证原子性,从而解决上面提到的可见性与原子性问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值