深入Go语言互斥锁的并发行为
本文分析Go语言sync.Mutex互斥锁的并发行为,重点探讨外层锁对内层锁获取的影响。以下代码片段展示了一个常见的场景:
package main import ( "fmt" "sync" "time" ) func main() { var mutex sync.Mutex wait := sync.WaitGroup{} fmt.Println("Locked") mutex.Lock() // 外层锁 for i := 1; i <= 3; i++ { wait.Add(1) go func(i int) { defer wait.Done() fmt.Printf("Not locked: %d ", i) mutex.Lock() fmt.Printf("Locked: %d ", i) time.Sleep(100 * time.Millisecond) mutex.Unlock() fmt.Printf("Unlocked: %d ", i) }(i) } fmt.Println("Unlocked") mutex.Unlock() // 释放外层锁 wait.Wait() }
这段代码中,主goroutine先获取mutex锁,然后启动三个goroutine,每个goroutine都尝试获取同一个锁。 很多人误以为外层锁会完全阻止内层锁的获取。
然而,实际运行结果并非如此:
Locked Not locked: 1 Not locked: 2 Not locked: 3 Unlocked Locked: 1 Unlocked: 1 Locked: 2 Unlocked: 2 Locked: 3 Unlocked: 3
结果表明,三个goroutine在尝试获取锁(mutex.Lock())之前,已经执行了fmt.Printf("Not locked: %d ", i)语句。 Unlocked的打印位置也证实了这一点:它在所有goroutine尝试获取锁但尚未成功之前打印。 之后,goroutine才依次获取锁,执行后续代码,并最终释放锁。
因此,外层锁并非没有影响内层锁,而是Go语言的并发调度机制导致goroutine在尝试获取锁前已执行部分代码。 外层锁的持有阻止了goroutine立即获取内层锁,但并不阻止goroutine执行其他代码。 只有在外层锁释放后,等待的goroutine才能依次获得锁。