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来进行并发编程,编写出更高效稳定的代码。


发布评论