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







程序员数学扫盲课
AI周刊:大模型、智能体与产业动态追踪
Claude Code 全体系指南:AI 编程智能体实战
Karpathy神经网络零基础课程
最新评论
开源的AI对话监控面板很实用,正好团队在找这类工具。准备试用一下。
折叠屏市场确实在升温,不过售罄也可能是备货策略。期待看到实际销量数据。
从磁盘I/O角度解释B树的设计动机,这个切入点很好。终于理解为什么数据库不用二叉树了。
IT术语转换确实是个痛点,之前用搜狗总是把技术词汇转成奇怪的词。智谱这个方向值得期待。
这个工具结合LLM和搜索API的思路很有意思,正好解决了我在做知识管理时遇到的问题。请问有没有部署文档?
这个漏洞确实严重,我们团队上周刚遇到类似问题。建议补充一下如何检测现有项目是否受影响的方法。
从简单规则涌现复杂性这个思路很有意思,让我想起元胞自动机。不过数字物理学在学术界争议还挺大的。
我也遇到了指令跟随变差的问题,特别是多轮对话时容易跑偏。不知道是模型退化还是负载优化导致的。