答案:sync.Mutex通过Lock和Unlock控制并发访问,防止竞态条件,确保共享资源安全,使用defer可避免忘记释放锁导致的死锁问题。

在Golang里,
sync.Mutex
sync.Mutex
Lock()
Unlock()
Lock()
Unlock()
一个经典的例子就是对一个计数器进行并发递增操作。没有锁的情况下,你会发现最终结果往往不符合预期:
package main
import (
"fmt"
"sync"
"time"
)
var counter int
func incrementWithoutLock() {
for i := 0; i < 1000; i++ {
counter++
}
}
func main() {
// 演示没有锁的情况
counter = 0
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
incrementWithoutLock()
}()
}
wg.Wait()
fmt.Printf("没有锁的最终计数: %d (预期: 10000)\n", counter) // 结果通常小于10000
// 使用互斥锁解决竞态条件
counter = 0 // 重置计数器
var mu sync.Mutex // 声明一个互斥锁
var wgWithLock sync.WaitGroup
for i := 0; i < 10; i++ {
wgWithLock.Add(1)
go func() {
defer wgWithLock.Done()
for j := 0; j < 1000; j++ {
mu.Lock() // 获取锁
counter++
mu.Unlock() // 释放锁
}
}()
}
wgWithLock.Wait()
fmt.Printf("使用互斥锁的最终计数: %d (预期: 10000)\n", counter) // 结果总是10000
// 更优雅的写法,使用defer确保解锁
counter = 0
var mu2 sync.Mutex
var wgWithDefer sync.WaitGroup
for i := 0; i < 10; i++ {
wgWithDefer.Add(1)
go func() {
defer wgWithDefer.Done()
for j := 0; j < 1000; j++ {
mu2.Lock()
defer mu2.Unlock() // 使用defer确保在函数返回前解锁,即使发生panic
counter++
}
}()
}
wgWithDefer.Wait()
fmt.Printf("使用defer互斥锁的最终计数: %d (预期: 10000)\n", counter)
// 演示一个稍微复杂点的场景:共享map
var sharedMap = make(map[string]int)
var mapMu sync.Mutex
var mapWg sync.WaitGroup
for i := 0; i < 5; i++ {
mapWg.Add(1)
go func(id int) {
defer mapWg.Done()
key := fmt.Sprintf("key_%d", id)
mapMu.Lock()
defer mapMu.Unlock()
sharedMap[key] = id * 10
time.Sleep(10 * time.Millisecond) // 模拟一些工作
fmt.Printf("Goroutine %d 写入 %s: %d\n", id, key, sharedMap[key])
}(i)
}
for i := 0; i < 5; i++ {
mapWg.Add(1)
go func(id int) {
defer mapWg.Done()
key := fmt.Sprintf("key_%d", id)
mapMu.Lock()
defer mapMu.Unlock()
if val, ok := sharedMap[key]; ok {
fmt.Printf("Goroutine %d 读取 %s: %d\n", id, key, val)
} else {
fmt.Printf("Goroutine %d 尝试读取 %s,但未找到\n", id, key)
}
time.Sleep(5 * time.Millisecond) // 模拟一些工作
}(i)
}
mapWg.Wait()
fmt.Println("共享Map最终内容:", sharedMap)
}说实话,很多人一开始接触并发编程,都会对“竞态条件”这个词感到有些抽象。简单来说,当多个goroutine(或者说线程)试图同时访问并修改同一个共享资源,而且这些操作的最终结果取决于它们执行的相对时序时,竞态条件就发生了。这就像多个人同时去抢一个座位,谁先坐下,谁就占了,但如果大家同时动,就可能出现混乱。
立即学习“go语言免费学习笔记(深入)”;
最常见的例子就是我上面提到的计数器。
counter++
counter
counter
设想一下,如果goroutine A读取到
counter
counter
counter
counter
互斥锁的作用,就是强制这些对共享资源的操作“串行化”。当一个goroutine获取了锁,它就拥有了对这块资源的“独占权”,其他想访问这块资源的goroutine就得排队等待。这样,上面的
counter++
互斥锁用起来简单,但要用好,避免“踩坑”,还是有些地方需要注意的。我个人就遇到过几次因为锁使用不当导致的死锁或者性能问题,那调试起来真是让人头大。
忘记解锁 (Forgetting to Unlock):这是最常见的错误之一。如果
Lock()
Unlock()
defer mu.Unlock()
死锁 (Deadlock):当两个或多个goroutine互相等待对方释放资源时,就会发生死锁。一个经典的例子是“哲学家就餐问题”。在实际代码中,这通常发生在尝试获取多个锁时,如果获取锁的顺序不一致,就很容易形成循环等待。
以上就是Golang mutex互斥锁使用方法与示例的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号