【Golang】Mutex源码解析

在 Go 中,锁的主要作用是保护共享资源,避免多个 goroutine 同时访问导致数据不一致。常见的锁有 sync.Mutex 和 sync.RWMutex

sync.Mutex源代码


type Mutex struct {
	state int32  //state用来表示互斥锁的状态
	sema  uint32 //sema (semaphore)类型的信号量,用于控制等待锁的 goroutine
}

// Locker 表示可以被锁定和解锁的对象。
type Locker interface {
	Lock()
	Unlock()
}

const (
	mutexLocked      = 1 << iota // mutex is locked 1开始不是0 mutexLocked 用来表示互斥锁已经被锁定
	mutexWoken                   //mutexWoken,表示互斥锁的等待者已经被唤醒,它的值是 1 << 1(2)。
	mutexStarving                //mutexStarving互斥锁处于饥饿模式,1 << 2(4)。
	mutexWaiterShift = iota      //mutexWaiterShift = iota 表示等待者数量的位从哪一位开始。因为这是 const 块的第四个声明,所以 iota 的值是 3,mutexWaiterShift 的值也是 3。这意味着在 state 变量中,等待者数量将从第 3 位开始计数。

	// m.state 的状态位分布:
	//mutexLocked(位 0):表示互斥锁是否被锁定。
	// mutexWoken(位 1):表示互斥锁的等待者是否被唤醒。
	// mutexStarving(位 2):表示互斥锁是否处于饥饿模式。
	// mutexWaiterShift(位 3 及以后):表示等待者数量的位从第 3 位开始计数。
	//下面是一个图像,展示了 m.state 的状态位分布:
// 	+--------+--------+--------+--------+--------+--------+--------+--------+
// | 31 ... | 7      | 6      | 5      | 4      | 3      | 2      | 1      | 0      |
// |     ...| waiter| waiter| waiter| mutexWoken | mutexStarving | mutexLocked |
// +--------+--------+--------+--------+--------+--------+--------+--------+

// 状态位示例
// 假设互斥锁被锁定,并且有 5 个 goroutine 在等待(即等待者数量为 5),同时互斥锁处于正常模式(非饥饿模式),m.state 的值可能是:
// +--------+--------+--------+--------+--------+--------+--------+--------+
// | 31 ... | 7      | 6      | 5      | 4      | 3      | 2      | 1      | 0      |
// +--------+--------+--------+--------+--------+--------+--------+--------+
// | ...    | 0      | 0      | 1      | 0      | 1      | 0      | 1      | 1      |
// +--------+--------+--------+--------+--------+--------+--------+--------+

	
	// Mutex 公平性。
	//
	// Mutex 可以处于两种模式:正常和饥饿。
	// 在正常模式下,等待者按 FIFO 顺序排队,但是唤醒的等待者并不拥有互斥锁,而是与新到达的 goroutine 竞争所有权。新到达的 goroutine 有优势——它们已经在 CPU 上运行,并且可能有很多,所以唤醒的等待者有很大的机会失去。在这种情况下,它会排在等待队列的前面。如果一个等待者超过 1ms 未能获得互斥锁,
	// 它将切换到饥饿模式。
	//
	// 在饥饿模式下,互斥锁的所有权直接从解锁的 goroutine 传递给队列前面的等待者。
	// 新到达的 goroutine 即使看起来互斥锁未锁定,也不会尝试获取互斥锁,也不会尝试自旋。相反,它们会将自己排在等待队列的末尾。
	//
	// 如果一个等待者获得了互斥锁的所有权,并看到要么
	// (1) 它是队列中的最后一个等待者,或者 (2) 它等待的时间少于 1ms,
	// 它将互斥锁切换回正常操作模式。
	//
	// 正常模式的性能明显更好,因为一个 goroutine 可以连续多次获取互斥锁,即使有被阻塞的等待者。
	// 饥饿模式对于防止尾部延迟的病理情况很重要。
	starvationThresholdNs = 1e6 //定义了饥饿模式的阈值,即如果一个等待者等待的时间超过 1 毫秒(1e6 纳秒),互斥锁将切换到饥饿模式。
)

// Lock 加锁 m。
// 如果锁已经被使用,调用 goroutine
// 会阻塞直到互斥锁可用。
func (m *Mutex) Lock() {
	// Fast path: grab unlocked mutex.
	// 快速路径:获取未锁定的互斥锁。
	if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
		//如果 CompareAndSwapInt32 返回 true,则表示互斥锁已经被成功锁定,并且如果启用了竞争检测(通过 race 包),则调用 race.Acquire 以记录这次锁的获取。
		if race.Enabled {
			race.Acquire(unsafe.Pointer(m)) //?
		}
		return
	}
	// Slow path (outlined so that the fast path can be inlined)
	// 慢速路径(为了使快速路径可以内联而单独列出)
	m.lockSlow()
}


// TryLock 尝试锁定 m 并报告是否成功。
//
// 注意,尽管 TryLock 的正确用法确实存在,但它们很少见,
// 使用 TryLock 通常是对互斥锁使用方式的更深层次问题的信号。
func (m *Mutex) TryLock() bool {
	old := m.state
	//检查 state 是否已经被锁定或处于饥饿模式。如果是,则返回 false。
	if old&(mutexLocked|mutexStarving) != 0 {
		return false
	}



	// 可能有一个 goroutine 正在等待互斥锁,但我们正在运行,可以尝试在那个 goroutine 唤醒之前获取互斥锁。
	if !atomic.CompareAndSwapInt32(&m.state, old, old|mutexLocked) {
		return false
	}

	if race.Enabled {
		race.Acquire(unsafe.Pointer(m))
	}
	return true
}

 race 检测器会输出一个报告,指出可能发生竞争的代码位置。如果没有数据竞争,程序将正常运行,不会输出任何关于 race 检测的信息。

    Mutex 公平性。

    Mutex 可以处于两种模式:正常和饥饿。

    在正常模式下,等待者按 FIFO 顺序排队,但是唤醒的等待者并不拥有互斥锁,而是与新到达的 goroutine 竞争所有权。新到达的 goroutine 有优势——它们已经在 CPU 上运行,并且可能有很多,所以唤醒的等待者有很大的机会失去。在这种情况下,它会排在等待队列的前面。如果一个等待者超过 1ms 未能获得互斥锁,

    它将切换到饥饿模式。

    在饥饿模式下,互斥锁的所有权直接从解锁的 goroutine 传递给队列前面的等待者。

    新到达的 goroutine 即使看起来互斥锁未锁定,也不会尝试获取互斥锁,也不会尝试自旋。相反,它们会将自己排在等待队列的末尾。

    它会首先执行需要锁保护的代码。在执行完这些代码并准备释放互斥锁之前

    如果一个等待者获得了互斥锁的所有权,并看到要么

    (1) 它是队列中的最后一个等待者,或者 (2) 它等待的时间少于 1ms,

    它将互斥锁切换回正常操作模式。

    正常模式的性能明显更好,因为一个 goroutine 可以连续多次获取互斥锁,即使有被阻塞的等待者。

    饥饿模式对于防止尾部延迟的病理情况很重要。

// runtime_canSpin 报告当前是否适合进行自旋。

//go:linkname sync_runtime_canSpin sync.runtime_canSpin
func sync_runtime_canSpin(i int) bool {
	if i >= active_spin || ncpu <= 1 || gomaxprocs <= int32(sched.npidle+sched.nmspinning)+1 {
		return false
	}
	if p := getg().m.p.ptr(); !runqempty(p) {
		return false
	}
	return true
}

 
if i >= active_spin || ncpu <= 1 || gomaxprocs <= int32(sched.npidle+sched.nmspinning)+1 {
    return false
}
  • active_spin 是一个阈值,表示在放弃自旋之前允许的最大自旋次数。如果当前的自旋次数 i 大于或等于这个阈值,函数返回 false,表示不应该再自旋。
  • ncpu 是系统中可用的 CPU 核心数。如果 ncpu 小于或等于 1,意味着没有多个核心可以利用,因此自旋没有意义,函数返回 false
  • gomaxprocs 是 runtime.GOMAXPROCS 的值,表示可以同时运行的最大 goroutine 数量。sched.npidle 是当前系统中空闲的 P(processor)数量,sched.nmspinning 是当前自旋的 M(machine)数量。如果 gomaxprocs 小于或等于空闲的 P 数量加上自旋的 M 数量加 1,意味着没有足够的工作需要处理,自旋没有意义,函数返回 false
  • 程序可以同时运行的最大 goroutine 数量<=当前有多少个空闲的处理器(P)+当前正在自旋的 goroutine 数量+当前正在尝试自旋的 goroutine(1)
  • 1.当有P空闲,可以直接调用P阻塞挂起减少CPU占用

  • 2.当P无空闲,自旋大于最大goroutine,无法运行

if p := getg().m.p.ptr(); !runqempty(p) {
    return false
}
  • getg().m.p.ptr() 获取当前 goroutine 运行的 P(processor)的指针。
  • runqempty(p) 检查该 P 的运行队列是否为空。如果不为空,意味着有其他 goroutine 正在等待运行,此时自旋会占用宝贵的 CPU 资源,因此函数返回 false

sync.Mutex底层实现图

读写锁(sync.RWMutex)

读写锁在互斥锁的基础上增加了对读取操作的优化。它允许任意数量的读取者(RLock())同时访问资源,但写入者(Lock())仍然享有独占访问权。sync.RWMutex提供了RLock()RUnlock()Lock()Unlock()方法。

sync.RWMutex 是 Go 语言标准库 sync 包中提供的一种读写锁,它用于控制对共享资源的并发访问。与普通的互斥锁(sync.Mutex)不同,读写锁允许多个读操作同时进行,但在写操作进行时,它会阻止其他读和写操作,确保写操作的独占访问权。这种锁非常适合读多写少的场景,因为它可以提高并发性能。

下面是 sync.RWMutex 提供的方法及其含义:

  1. RLock():读锁定。当一个 goroutine 想要读取资源时,它会调用 RLock() 方法来尝试获取锁。如果锁当前没有被写入者持有,那么这个读锁定请求会成功,允许多个读操作同时进行。

  2. RUnlock():读解锁。当一个 goroutine 完成对资源的读取后,它会调用 RUnlock() 方法来释放锁。每次 RLock() 调用都需要有对应的 RUnlock() 调用,以确保锁能够被正确地释放。

  3. Lock():写锁定。当一个 goroutine 想要修改资源时,它会调用 Lock() 方法来尝试获取锁。如果锁当前没有被任何读或写操作持有,那么这个写锁定请求会成功,并且这个 goroutine 将独占访问权,直到它调用 Unlock()

  4. Unlock():写解锁。当一个 goroutine 完成对资源的修改后,它会调用 Unlock() 方法来释放锁,允许其他等待的读或写操作获取锁。

sync.RWMutex 的工作原理如下:

  • 读锁:如果 sync.RWMutex 的状态表明没有写锁被持有(即没有 goroutine 调用了 Lock()),那么可以安全地增加读锁计数(通常通过增加 state 字段的值)。如果有其他 goroutine 已经持有读锁,那么新的读锁请求也可以成功,因为读锁之间不会相互阻塞。
  • 写锁:如果 sync.RWMutex 的状态表明没有其他读锁或写锁被持有,那么写锁请求可以成功。一旦写锁被持有,任何新的读锁或写锁请求都将被阻塞,直到写锁被释放。
  • 锁释放:当读锁被释放时(通过 RUnlock()),读锁计数会减少。当最后一个读锁被释放或者写锁被释放时(通过 Unlock()),锁的状态会被更新,以允许等待的读锁或写锁请求获取锁。

使用例子

以下是一个简单的例子,演示如何用 sync.Mutex 保护共享数据(如计数器):

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex   // 创建一个互斥锁
	counter := 0         // 共享资源
	var wg sync.WaitGroup

	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			mu.Lock()          // 加锁
			counter++          // 修改共享资源
			mu.Unlock()        // 解锁
			wg.Done()
		}()
	}

	wg.Wait()
	fmt.Println("Counter:", counter)
}

解释

  • mu.Lock():在修改 counter 之前加锁,确保只有一个 goroutine 能够访问 counter
  • counter++:对共享资源进行修改。
  • mu.Unlock():完成修改后解锁,让其他 goroutine 可以访问 counter

这样可以避免多个 goroutine 同时修改 counter 导致的数据错误问题。

下面是没有加锁的代码版本,看看会发生什么情况:

package main

import (
	"fmt"
	"sync"
)

func main() {
	counter := 0         // 共享资源
	var wg sync.WaitGroup

	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			counter++          // 修改共享资源(没有加锁)
			wg.Done()
		}()
	}

	wg.Wait()
	fmt.Println("Counter:", counter)
}

解释

  • 由于没有加锁,多个 goroutine 可能会在同一时间访问 counter,导致 数据竞争 问题。
  • 因为 counter++ 操作不是原子的(即,不是一步完成的操作),多个 goroutine 在读取、增加和写入 counter 的时候可能会互相干扰。
  • 结果是,最终的 counter 值可能小于预期的 5,因为有些增量操作被覆盖或跳过了。

示例输出

运行上面的代码可能会得到各种不同的结果,例如:

  • Counter: 3
  • Counter: 4
  • Counter: 5

因为数据竞争,counter 最终值并不可靠,也不总是预期的结果 5。这就是加锁的必要性,通过加锁可以确保每次只有一个 goroutine 修改 counter,避免数据竞争带来的不一致。

源码

// 版权所有 2009 Go 语言作者。版权所有。
// 使用此源代码受 BSD 风格
// 许可证的约束,详情见 LICENSE 文件。

// sync 包提供了基本的同步原语,比如互斥锁。除了 Once 和 WaitGroup 类型外,大多数都是供低级库例程使用的。更高级别的同步最好通过通道和通信来完成。
//
// 包含此包中定义的类型的值不应被复制。
package sync

import (
	"internal/race"
	"sync/atomic"
	"unsafe"
)

// 由运行时通过 linkname 提供。
func throw(string)
func fatal(string)

// 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.
// Mutex 是一个互斥锁。
// Mutex 的零值是一个未锁定的互斥锁。
//
// Mutex 在首次使用后不得被复制。
//
// 在 Go 内存模型的术语中,
// 第 n 次调用 Unlock “在第 m 次调用 Lock 之前同步”
// 对于任何 n < m。
// 成功调用 TryLock 等同于调用 Lock。
// 失败的 TryLock 调用则根本不建立任何 “在...之前同步” 的关系。
type Mutex struct {
	state int32  //state用来表示互斥锁的状态
	sema  uint32 //sema (semaphore)类型的信号量,用于控制等待锁的 goroutine
}

// Locker 表示可以被锁定和解锁的对象。
type Locker interface {
	Lock()
	Unlock()
}

const (
	mutexLocked      = 1 << iota // mutex is locked 1开始不是0 mutexLocked 用来表示互斥锁已经被锁定
	mutexWoken                   //mutexWoken,表示互斥锁的等待者已经被唤醒,它的值是 1 << 1(2)。
	mutexStarving                //mutexStarving互斥锁处于饥饿模式,1 << 2(4)。
	mutexWaiterShift = iota      //mutexWaiterShift = iota 表示等待者数量的位从哪一位开始。因为这是 const 块的第四个声明,所以 iota 的值是 3,mutexWaiterShift 的值也是 3。这意味着在 state 变量中,等待者数量将从第 3 位开始计数。

	// m.state 的状态位分布:
	//mutexLocked(位 0):表示互斥锁是否被锁定。
	// mutexWoken(位 1):表示互斥锁的等待者是否被唤醒。
	// mutexStarving(位 2):表示互斥锁是否处于饥饿模式。
	// mutexWaiterShift(位 3 及以后):表示等待者数量的位从第 3 位开始计数。
	//下面是一个图像,展示了 m.state 的状态位分布:
	// 	+--------+--------+--------+--------+--------+--------+--------+--------+
	// | 31 ... | 7      | 6      | 5      | 4      | 3      | 2      | 1      | 0      |
	// |     ...| waiter| waiter| waiter| mutexWoken | mutexStarving | mutexLocked |
	// +--------+--------+--------+--------+--------+--------+--------+--------+

	// 状态位示例
	// 假设互斥锁被锁定,并且有 5 个 goroutine 在等待(即等待者数量为 5),同时互斥锁处于正常模式(非饥饿模式),m.state 的值可能是:
	// +--------+--------+--------+--------+--------+--------+--------+--------+
	// | 31 ... | 7      | 6      | 5      | 4      | 3      | 2      | 1      | 0      |
	// +--------+--------+--------+--------+--------+--------+--------+--------+
	// | ...    | 0      | 0      | 1      | 0      | 1      | 0      | 1      | 1      |
	// +--------+--------+--------+--------+--------+--------+--------+--------+

	// Mutex fairness.
	//
	// Mutex can be in 2 modes of operations: normal and starvation.
	// In normal mode waiters are queued in FIFO order, but a woken up waiter
	// does not own the mutex and competes with new arriving goroutines over
	// the ownership. New arriving goroutines have an advantage -- they are
	// already running on CPU and there can be lots of them, so a woken up
	// waiter has good chances of losing. In such case it is queued at front
	// of the wait queue. If a waiter fails to acquire the mutex for more than 1ms,
	// it switches mutex to the starvation mode.
	//
	// In starvation mode ownership of the mutex is directly handed off from
	// the unlocking goroutine to the waiter at the front of the queue.
	// New arriving goroutines don't try to acquire the mutex even if it appears
	// to be unlocked, and don't try to spin. Instead they queue themselves at
	// the tail of the wait queue.
	//
	// If a waiter receives ownership of the mutex and sees that either
	// (1) it is the last waiter in the queue, or (2) it waited for less than 1 ms,
	// it switches mutex back to normal operation mode.
	//
	// Normal mode has considerably better performance as a goroutine can acquire
	// a mutex several times in a row even if there are blocked waiters.
	// Starvation mode is important to prevent pathological cases of tail latency.
	// Mutex 公平性。
	//
	// Mutex 可以处于两种模式:正常和饥饿。
	// 在正常模式下,等待者按 FIFO 顺序排队,但是唤醒的等待者并不拥有互斥锁,而是与新到达的 goroutine 竞争所有权。新到达的 goroutine 有优势——它们已经在 CPU 上运行,并且可能有很多,所以唤醒的等待者有很大的机会失去。在这种情况下,它会排在等待队列的前面。如果一个等待者超过 1ms 未能获得互斥锁,
	// 它将切换到饥饿模式。
	//
	// 在饥饿模式下,互斥锁的所有权直接从解锁的 goroutine 传递给队列前面的等待者。
	// 新到达的 goroutine 即使看起来互斥锁未锁定,也不会尝试获取互斥锁,也不会尝试自旋。相反,它们会将自己排在等待队列的末尾。
	//
	// 如果一个等待者获得了互斥锁的所有权,并看到要么
	// (1) 它是队列中的最后一个等待者,或者 (2) 它等待的时间少于 1ms,
	//它会首先执行需要锁保护的代码。在执行完这些代码并准备释放互斥锁之前
	// 它将互斥锁切换回正常操作模式。
	//
	// 正常模式的性能明显更好,因为一个 goroutine 可以连续多次获取互斥锁,即使有被阻塞的等待者。
	// 饥饿模式对于防止尾部延迟的病理情况很重要。
	starvationThresholdNs = 1e6 //定义了饥饿模式的阈值,即如果一个等待者等待的时间超过 1 毫秒(1e6 纳秒),互斥锁将切换到饥饿模式。
)

// Lock 加锁 m。
// 如果锁已经被使用,调用 goroutine
// 会阻塞直到互斥锁可用。
func (m *Mutex) Lock() {
	// Fast path: grab unlocked mutex.
	// 快速路径:获取未锁定的互斥锁。
	if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
		//如果 CompareAndSwapInt32 返回 true,则表示互斥锁已经被成功锁定,并且如果启用了竞争检测(通过 race 包),则调用 race.Acquire 以记录这次锁的获取。
		if race.Enabled {
			race.Acquire(unsafe.Pointer(m)) //?
		}
		return
	}
	// Slow path (outlined so that the fast path can be inlined)
	// 慢速路径(为了使快速路径可以内联而单独列出)
	//锁是锁定的通过自旋等待队列等方式加锁
	m.lockSlow()
}

// TryLock tries to lock m and reports whether it succeeded.
//
// Note that while correct uses of TryLock do exist, they are rare,
// and use of TryLock is often a sign of a deeper problem
// in a particular use of mutexes.
// TryLock 尝试锁定 m 并报告是否成功。
//
// 注意,尽管 TryLock 的正确用法确实存在,但它们很少见,
// 使用 TryLock 通常是对互斥锁使用方式的更深层次问题的信号。

func (m *Mutex) TryLock() bool {
	old := m.state
	//检查 state 是否已经被锁定或处于饥饿模式。如果是,则返回 false。
	if old&(mutexLocked|mutexStarving) != 0 {
		return false
	}

	// There may be a goroutine waiting for the mutex, but we are
	// running now and can try to grab the mutex before that
	// goroutine wakes up.

	// 可能有一个 goroutine 正在等待互斥锁,但我们正在运行,可以尝试在那个 goroutine 唤醒之前获取互斥锁。
	if !atomic.CompareAndSwapInt32(&m.state, old, old|mutexLocked) {
		return false
	}

	if race.Enabled {
		race.Acquire(unsafe.Pointer(m))
	}
	return true
}

func (m *Mutex) lockSlow() {
	var waitStartTime int64
	starving := false
	awoke := false
	iter := 0
	old := m.state
	for {
		if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
			if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
				atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
				awoke = true
			}
			runtime_doSpin()
			iter++
			old = m.state
			continue
		}
		new := old
		if old&mutexStarving == 0 {
			new |= mutexLocked
		}
		if old&(mutexLocked|mutexStarving) != 0 {
			new += 1 << mutexWaiterShift
		}
		if starving && old&mutexLocked != 0 {
			new |= mutexStarving
		}
		if awoke {
			if new&mutexWoken == 0 {
				throw("sync: inconsistent mutex state")
			}
			new &^= mutexWoken
		}
		if atomic.CompareAndSwapInt32(&m.state, old, new) {
			if old&(mutexLocked|mutexStarving) == 0 {
				break
			}

			queueLifo := waitStartTime != 0
			if waitStartTime == 0 {
				waitStartTime = runtime_nanotime()
			}
			runtime_SemacquireMutex(&m.sema, queueLifo, 1)
			starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
			old = m.state
			if old&mutexStarving != 0 {
				if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
					throw("sync: inconsistent mutex state")
				}
				delta := int32(mutexLocked - 1<<mutexWaiterShift)
				if !starving || old>>mutexWaiterShift == 1 {
					delta -= mutexStarving
				}
				atomic.AddInt32(&m.state, delta)
				break
			}
			awoke = true
			iter = 0
		} else {
			old = m.state
		}
	}

	if race.Enabled {
		race.Acquire(unsafe.Pointer(m))
	}
}

func (m *Mutex) Unlock() {
	if race.Enabled {
		_ = m.state
		race.Release(unsafe.Pointer(m))
	}

	// Fast path: drop lock bit.
	// 快速路径:释放锁位。
	new := atomic.AddInt32(&m.state, -mutexLocked)
	if new != 0 {
		m.unlockSlow(new)
	}
}

func (m *Mutex) unlockSlow(new int32) {
	if (new+mutexLocked)&mutexLocked == 0 {
		fatal("sync: unlock of unlocked mutex")
	}
	if new&mutexStarving == 0 {
		old := new
		for {
			if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
				return
			}
			new = (old - 1<<mutexWaiterShift) | mutexWoken
			if atomic.CompareAndSwapInt32(&m.state, old, new) {
				runtime_Semrelease(&m.sema, false, 1)
				return
			}
			old = m.state
		}
	} else {
		runtime_Semrelease(&m.sema, true, 1)
	}
}

mutex模拟实现

可参考 https://www.nowcoder.com/discuss/518011493254692864

https://zhuanlan.zhihu.com/p/27608263

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值