跳到主要内容

Channel vs Mutex

问题

Go 中 Channel 和 Mutex 都能处理并发问题,什么场景该用 Channel,什么场景该用 Mutex?

答案

核心区别

维度ChannelMutex
理念通过通信共享内存(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 死锁

相关链接