Channel vs Mutex
问题
Go 中 Channel 和 Mutex 都能处理并发问题,什么场景该用 Channel,什么场景该用 Mutex?
答案
核心区别
| 维度 | Channel | Mutex |
|---|---|---|
| 理念 | 通过通信共享内存(CSP) | 通过共享内存通信 |
| 用途 | goroutine 间传递数据、协调流程 | 保护共享状态的临界区 |
| 阻塞行为 | 发送/接收天然阻塞 | Lock 时阻塞 |
| 所有权 | 数据在 goroutine 间转移所有权 | 数据被多方共享访问 |
| 形象比喻 | 传纸条 | 抢话筒 |
选 Channel 的场景
当你需要在 goroutine 之间传递数据或协调执行顺序时,用 Channel:
// 1. 生产者-消费者
jobs := make(chan Job, 100)
go producer(jobs)
go consumer(jobs)
// 2. 通知/信号
done := make(chan struct{})
go func() {
doWork()
close(done) // 通知完成
}()
<-done
// 3. 管道/流水线
output := stage2(stage1(input))
// 4. 限流(带缓冲 Channel 做信号量)
sem := make(chan struct{}, 10)
// 5. 超时控制
select {
case result := <-resultCh:
use(result)
case <-time.After(3 * time.Second):
handleTimeout()
}
选 Mutex 的场景
当你需要保护被多个 goroutine 共享的状态时,用 Mutex:
// 1. 共享缓存
type Cache struct {
mu sync.RWMutex
data map[string]any
}
func (c *Cache) Get(key string) (any, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
v, ok := c.data[key]
return v, ok
}
func (c *Cache) Set(key string, val any) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = val
}
// 2. 计数器
type Counter struct {
mu sync.Mutex
count int
}
// 3. 状态机
type Connection struct {
mu sync.Mutex
state string
}
决策图
性能对比
// Benchmark 结果(参考值,具体取决于场景)
BenchmarkMutexCounter-8 100000000 11.5 ns/op
BenchmarkChannelCounter-8 20000000 62.3 ns/op
BenchmarkAtomicCounter-8 200000000 5.8 ns/op
atomic最快,但只能操作单个变量Mutex比 Channel 快约 5x,适合保护共享状态Channel有额外的调度开销,但代码更清晰
Go 谚语
Don't communicate by sharing memory; share memory by communicating.
不要通过共享内存来通信,而是通过通信来共享内存。
这不是说 Mutex 不好,而是在设计并发架构时,优先考虑用 Channel 传递数据的所有权,而不是让多个 goroutine 直接操作共享变量。
混合使用
实际项目中两者常常配合使用:
type WorkerPool struct {
mu sync.Mutex
workers int // Mutex 保护共享状态
jobs chan Job // Channel 传递任务
results chan Result // Channel 传递结果
}
常见面试问题
Q1: 一句话总结 Channel 和 Mutex 的适用场景?
答案:
- Channel:传递数据、协调流程(做通信用)
- Mutex:保护共享状态(做锁用)
Q2: 为什么不推荐用 Channel 做简单的计数器?
答案:
// ❌ 过度使用 Channel
func channelCounter() {
ch := make(chan int, 1)
ch <- 0
increment := func() {
v := <-ch
ch <- v + 1
}
}
// ✅ 简单用 atomic 或 Mutex
var counter atomic.Int64
counter.Add(1)
Channel 有 goroutine 调度开销,对于简单的状态保护是杀鸡用牛刀。
Q3: Channel 死锁和 Mutex 死锁的区别?
答案:
- Channel 死锁:发送方和接收方都在等待对方(如无缓冲 Channel 同一个 goroutine 先发后收)。Go 运行时能检测全局死锁并 panic
- Mutex 死锁:goroutine A 持有锁 1 等锁 2,goroutine B 持有锁 2 等锁 1。Go 运行时不能检测 Mutex 死锁