2025-12-26 · 实战
32
实战 · 2025-12-26

Go语言锁机制深度指南

Go语言锁机制深度指南

一、并发与并行

并发(Concurrency):同时处理多个任务
并行(Parallelism):同时执行多个任务

Go的并发模型:
- Goroutines:轻量级线程
- Channels:通信管道

二、为什么需要锁

数据竞争

多个goroutine同时访问共享数据,至少一个是写操作,就会产生数据竞争。

问题:结果不可预测

解决:用锁保护共享数据

锁的核心操作

lock.Lock()      // 获取锁
// 临界区代码
lock.Unlock()    // 释放锁

三、sync.Mutex:互斥锁

基本用法

import "sync"

var mu sync.Mutex
var count int

func increment() {
    mu.Lock()
    count++
    mu.Unlock()
}

TryLock(Go 1.18+)

非阻塞获取锁:

if mu.TryLock() {
    // 获取成功,执行操作
    defer mu.Unlock()
} else {
    // 获取失败,处理其他逻辑
}

检测数据竞争

go run -race main.go

四、sync.RWMutex:读写锁

适用于读多写少的场景。

API说明

方法
说明

RLock/RUnlock
获取/释放读锁(多个读可以同时持有)

Lock/Unlock
获取/释放写锁(独占)

使用示例

var rw sync.RWMutex
var data map[string]string

func read(key string) string {
    rw.RLock()
    defer rw.RUnlock()
    return data[key]
}

func write(key, value string) {
    rw.Lock()
    defer rw.Unlock()
    data[key] = value
}

何时选择RWMutex

五、常见陷阱

1. 死锁

// ❌ 错误:重复加锁
mu.Lock()
mu.Lock()  // 死锁!
// ❌ 错误:锁顺序不一致
// goroutine 1
mu1.Lock()
mu2.Lock()

// goroutine 2
mu2.Lock()
mu1.Lock()  // 可能死锁

2. 忘记Unlock

// ❌ 危险
mu.Lock()
if someCondition {
    return  // 忘记解锁!
}
mu.Unlock()
// ✅ 正确:用defer
mu.Lock()
defer mu.Unlock()
if someCondition {
    return
}

3. 临界区范围不当

// ❌ 临界区太大
mu.Lock()
data = fetchFromDB()  // I/O操作在锁内!
mu.Unlock()
// ✅ 正确:临界区只保护共享数据
temp := fetchFromDB()
mu.Lock()
data = temp
mu.Unlock()

六、最佳实践

1. 保持临界区简短

锁内只做必要的共享数据操作。

2. 选择合适的锁粒度

粒度
优点
缺点

粗粒度
简单,不易出错
并发度低

细粒度
并发度高
复杂,易死锁

3. 在结构体中嵌入锁

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

4. 避免在持有锁时阻塞

不要在锁内进行:
- 网络I/O
- 文件I/O
- 通道操作(可能阻塞)
- time.Sleep

七、其他同步原语

sync/atomic

适用于简单的计数器、标志位:

import "sync/atomic"

var count int64
atomic.AddInt64(&count, 1)

Channels

Go推荐的方式:通过通信共享内存

ch := make(chan int, 1)
ch <- value  // 发送
v := <-ch    // 接收

sync.WaitGroup

等待一组goroutine完成:

var wg sync.WaitGroup
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        // 工作
    }()
}
wg.Wait()

八、性能分析

用pprof分析锁竞争:

import _ "net/http/pprof"

func main() {
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()
    // 应用代码
}

访问:http://localhost:6060/debug/pprof/

九、总结

锁类型
适用场景

sync.Mutex
通用互斥锁

sync.RWMutex
读多写少

sync/atomic
简单计数器

Channel
通信同步

核心原则
1. 临界区尽可能小
2. 用defer避免忘记解锁
3. 统一加锁顺序避免死锁
4. 优先用Channel通信


参考链接
- 原文:https://docs.80aj.com/docs/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%20Go%20%E8%AF%AD%E8%A8%80%E4%B8%AD%E7%9A%84%E9%94%81%E6%9C%BA%E5%88%B6.html

目录 最新
← 左侧翻上一屏 · 右侧翻下一屏 · 中间唤出菜单