章节

11.2 同步锁

本篇学习 sync.Mutex 的加锁与解锁方式,并能保护并发读写的临界区。

同步锁

概念说明

sync.Mutex 是互斥锁。
同一时间只允许一个 goroutine 进入被锁保护的代码区域。

这段被保护的代码区域通常叫临界区。
临界区里一般会读写共享变量、map、切片等数据。

语法/规则

  1. 使用 mu.Lock() 加锁。
  2. 使用 mu.Unlock() 解锁。
  3. 推荐在加锁后立即写 defer mu.Unlock(),避免提前返回忘记解锁。
  4. 锁保护的是 Lock()Unlock() 之间这段共享数据操作,不是整个 goroutine。
  5. 不要复制已经使用过的 sync.Mutex

场景示例:两个 goroutine 都要修改同一个计数器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package main

import (
	"fmt"
	"sync"
	"time"
)

func writeFromA(mu *sync.Mutex, wg *sync.WaitGroup, count *int) {
	defer wg.Done()
	fmt.Println("A: want lock") // 这里还没有加锁

	mu.Lock()
	fmt.Println("A: get lock") // A 拿到锁后,其他 goroutine 只能在 Lock 处等待

	current := *count
	time.Sleep(100 * time.Millisecond) // 为了观察等待效果,这里故意放慢;实际开发不建议把耗时操作放在锁内
	*count = current + 1
	fmt.Println("A: write count =", *count)

	mu.Unlock() // 到这里才真正解锁,锁保护范围结束
}

func writeFromB(mu *sync.Mutex, wg *sync.WaitGroup, count *int) {
	defer wg.Done()
	fmt.Println("B: want lock") // 这里还没有加锁

	mu.Lock()
	fmt.Println("B: get lock") // 只有 A 解锁后,B 才能拿到锁

	current := *count
	*count = current + 1
	fmt.Println("B: write count =", *count)

	mu.Unlock() // 到这里解锁
}

func main() {
	var mu sync.Mutex
	var wg sync.WaitGroup
	count := 0

	wg.Add(1)
	go writeFromA(&mu, &wg, &count)

	time.Sleep(10 * time.Millisecond) // 给 A 一点时间先拿到锁,便于观察锁的效果

	wg.Add(1)
	go writeFromB(&mu, &wg, &count)

	wg.Wait()
	fmt.Println("final count:", count)
}

输出结果(为了便于观察,下面是一种常见结果):

1
2
3
4
5
6
7
A: want lock
A: get lock
B: want lock
A: write count = 1
B: get lock
B: write count = 2
final count: 2

锁保护的范围在哪里

writeFromA 里的这几行:

1
2
3
4
5
mu.Lock()
current := *count
time.Sleep(100 * time.Millisecond)
*count = current + 1
mu.Unlock()

锁保护的就是这段范围:

1
2
3
mu.Lock()   开始加锁
...
mu.Unlock() 结束解锁

也就是说:

  1. fmt.Println("A: want lock") 不在锁保护范围内。
  2. current := *count*count = current + 1 在锁保护范围内。
  3. writeFromB 执行到 mu.Lock() 时,如果 A 还没解锁,就只能等待。
  4. 不是“锁住了 A 这个 goroutine”,而是“谁拿到同一把锁,谁才能进入这段共享数据操作”。

这个示例体现了什么

  1. writeFromAwriteFromB 是两个不同函数,它们都在修改同一个 count
  2. 没有锁时,它们可能同时读写 count,从而产生数据竞争。
  3. 加锁后,同一时间只有一个 goroutine 能进入 count 的读写区域。
  4. 这就是 Mutex 的独特作用:让共享数据的修改变成“一个一个来”。

常见错误

  1. 加锁后忘记解锁,导致其他 goroutine 永久阻塞。
  2. 锁的范围过大,把耗时 IO 也放在锁内,降低并发性能。
  3. 在不同地方用不同锁保护同一份数据,导致保护失效。
  4. 复制带锁的结构体,可能造成锁状态混乱。
本文禁止转载
使用 Hugo 构建
主题 StackJimmy 设计 由 Hobin 魔改
最近构建时间:2026-04-17 19:07:48 CST
载入天数...载入时分秒...
发表了 1 篇文章 · 发表了 152 篇笔记 · 总计 18 万 0 千字