go的互斥锁Mutex
1.基本用法
当多个 goroutine 需要访问共享数据时,可以使用 sync.Mutex 来保护数据。
var counter int = 0
var mu sync.Mutex
func main() {
for i := 0; i < 1000; i++ {
go func() {
mu.Lock()
counter++
mu.Unlock()
}()
}
time.Sleep(time.Second)
fmt.Println(counter)
}
代码运行逻辑
- 主 goroutine 启动 1000 个子 goroutine。
- 每个子 goroutine 尝试获取互斥锁
mu,如果锁已被其他 goroutine 持有,则阻塞等待。 - 成功获取锁的 goroutine 修改
counter,将counter加 1。 - 修改完成后,释放锁,允许其他 goroutine 继续执行。
- 主 goroutine 等待 1 秒(确保所有子 goroutine 完成),然后打印
counter的最终值。
关键点
- 互斥锁的作用:
- 保证同一时间只有一个 goroutine 可以修改
counter,避免了竞争条件。
- 保证同一时间只有一个 goroutine 可以修改
- 竞争条件:
- 如果没有互斥锁,多个 goroutine 同时修改
counter,可能导致最终值小于 1000。 - 例如,两个 goroutine 同时读取
counter的值为 10,然后分别加 1,最终counter的值是 11 而不是 12。
- 如果没有互斥锁,多个 goroutine 同时修改
- 等待 goroutine 完成:
- 使用
time.Sleep(time.Second)是一种简单的等待方式,但不精确。 - 更推荐使用
sync.WaitGroup来精确控制 goroutine 的同步。
- 使用
2. 改进版本(使用 sync.WaitGroup
以下是使用 sync.WaitGroup 的改进版本,可以更精确地等待所有 goroutine 完成:
package main
import (
"fmt"
"sync"
)
var counter int = 0
var mu sync.Mutex
var wg sync.WaitGroup
func main() {
wg.Add(1000) // 设置需要等待的 goroutine 数量
for i := 0; i < 1000; i++ {
go func() {
defer wg.Done() // goroutine 完成后通知 WaitGroup
mu.Lock()
counter++
mu.Unlock()
}()
}
wg.Wait() // 等待所有 goroutine 完成
fmt.Println(counter)
}
在 go func() { ... }() 中:
func() { ... }定义了一个匿名函数。()立即执行这个匿名函数。go关键字将这个匿名函数的执行放在一个新的 goroutine 中并发运行。- 最后的
()是必不可少的,它确保了匿名函数的立即执行。
3.匿名函数不太习惯,修改为具名函数
如果希望在循环外部单独定义函数,并在循环中执行这个函数,可以将匿名函数定义为具名函数,然后在循环中通过 go 关键字启动它。以下是修改后的代码:
package main
import (
"fmt"
"sync"
)
var counter int = 0
var mu sync.Mutex
var wg sync.WaitGroup
// 单独定义函数
func incrementCounter() {
defer wg.Done() // goroutine 完成后通知 WaitGroup
mu.Lock()
counter++
mu.Unlock()
}
func main() {
wg.Add(1000) // 设置需要等待的 goroutine 数量
for i := 0; i < 1000; i++ {
go incrementCounter() // 在循环中执行定义的函数
}
wg.Wait() // 等待所有 goroutine 完成
fmt.Println(counter)
}
努力生活,融于自然

浙公网安备 33010602011771号