2024年1月31日发(作者:)

golang runtime_semacquiremutex实现原理

在Go语言中,Mutex是一个非常常用的同步原语,用于对共享资源进行加锁和解锁操作。而在Mutex的实现中,其中的核心函数之一就是uiremutex。

首先,我们需要了解一下Go语言中Mutex的基本概念和用法。Mutex是Go语言中用来进行互斥锁操作的一个结构体,其定义如下:

type Mutex struct {

state int32

sema uint32

}

其中,state表示锁的状态,sema表示在加锁时等待的信号量。

当我们调用Mutex的Lock方法时,会首先进行一次快速加锁尝试。如果能够成功获取到锁,那么就直接返回;否则,就需要调用具体的加锁函数,即uiremutex。接下来,我们就来详细介绍一下uiremutex的实现原理。

在Go语言中,uiremutex函数的实现位于

src/runtime/文件中。下面是它的部分代码:

Ensure that the calling goroutine has acquired necessary locks to

modify *addr. This is nosplit so that it can be called from the

scheduler.

This function is called when acquiring a count of 0 semas.

nosplit because it manipulates g→atomicstatus or curg→waitreason.

runtime·semasleep and handoff are nosplit but are invoked via

goready, gopark, and gowait in non-preemptible contexts.

临界区加锁函数

做检查最, 快速加锁, 不行了才会进行真正的获取锁, 并park

这个函数调用前会紧临在Lock中调用, 是系统调用的系列

也就是说这个函数只会在进入到Lock函数时执行一次

不会通过函数的反复调用

阻塞手动将goroutine拉入运行队列>释放排他锁

不是为了能上操作系统也支持的线程安全排他锁

Illustrated mutex fairness algo:

Brief history:

Before 1.7, goroutines acquire lock sema with spin, which could cause

starvation, for example, it can live-lock

Buffered chan, which is a common live lock situation for go developers:

ch := make(chan bool, 1)

func lockDeadLockSituation() {

ch <- true 1st

<-ch 2nd

}

Following is the deadlock illustrate:

goroutine 1 goroutine 2

Lock() lock i+0(semacquire(&i)) Lock() lock

k+0(semacquire(&k))

In the 1st step of lockDeadLocksituation:

goroutine 1 goroutine 2

Lock() STORE i+0(semacquire(&i)) Lock() LOAD

i+0(semacquire(&i))

The cames from lockDeadLockSituation prefixed off

runtime.

thread(1) => doloopLock(i: int32) thread(2) => doloopLock(k:

int32)

IAPPEND < runtime Lock LOCK < runtime Lock

LOCK > runtime Lock IAPPEND > runtime Lock

LOCK < runtime Lock LOCK < runtime 心痛

uireMutexSlowpath is the slow path to finalize the

mutex

acquire or untimely wake up concurrent sleepers.

func runtime·semasleep1(addr *uint32, wait *waitq, msec int32)

{

......

}

根据注释,我们可以看到uiremutex作为临界区加锁函数,会进行以下步骤:

1. 首先,它会尝试进行快速加锁。具体来说,它会先检查锁的当前状态,如果锁的状态是0(表示没有被持有),则将锁的状态设置为-1,并立即返回。

2. 如果快速加锁失败,即锁的状态不为0,则会进入下一阶段,即真正的获取锁。

3. 在获取锁的过程中,会使用一个等待队列(waitq)来暂时存储需要等待锁释放的goroutine,它会调用函数eep1来完成等待操作。这个函数会将当前goroutine加入到waitq中,并通过park函数将当前goroutine转为等待状态,等待锁的释放。

4. 在真正获取到锁之前,被park的goroutine是不能被调度器调度的,也就是说,它会一直等待,直到锁的持有者解锁该锁。

5. 当锁的持有者解锁该锁时,解锁操作会通过调用函数leasemutex来进行。这个函数会从等待队列中取出goroutine,并将其转为可运行状态。这样,被park的goroutine就可以被调度器重新调度。

通过上述的分析,可以总结出uiremutex的实现原理:当锁不能通过快速加锁得到时,会将当前goroutine暂时加入到等待队列中,然后进入休眠状态,等待锁的释放。在锁的持有者释放锁之后,会将等待队列中的

goroutine唤醒,并转为可运行状态。

在Go语言中,使用Mutex来实现并发控制是非常重要的,而uiremutex作为Mutex的核心函数之一,负责处理锁的加锁逻辑,保证了并发场景下的资源安全性。通过深入理解和分析uiremutex的原理,我们可以更好地使用Mutex来进行并发编程,编写出更高效稳定的代码。