优先根据场景选择:保护共享数据用 sync.Mutex,传递值用 chan;前者性能高、易封装,后者适合任务分发与多路复用。

该用 sync.Mutex 还是 chan?先问自己一句话
你是想「保护一块随时被读写的共享数据」,还是想「把一个值从 A goroutine 交给 B goroutine」?
前者选 sync.Mutex(或 sync.RWMutex),后者选 chan。这个判断比背规则快得多,也准得多。
频繁读写计数器、map、缓存?别碰 chan,直接上 sync.Mutex
常见错误:有人用带缓冲的 chan int 模拟原子计数器,比如只开容量为 1 的 channel 当信号量用——这既难读又慢,还容易死锁。
-
sync.Mutex加解锁只是几个原子操作,无 goroutine 切换开销;chan发送/接收会触发调度器介入,性能差一到两个数量级 - map 不是并发安全的,
map[string]int被多个 goroutine 写就会 panic:fatal error: concurrent map writes,必须用mu.Lock()包住读写逻辑 - 如果你封装了
SafeMap或RateLimiter,用 mutex 更自然:方法内加锁、返回前解锁,调用方完全无感
要分发任务、等结果、做超时控制?chan 是唯一正解
常见错误:用全局变量 + mutex + for 循环轮询来“等待任务完成”,既浪费 CPU,又无法响应取消或超时。
- 生产者-消费者模型天然匹配
chan:jobs := make(chan int, 10)分配任务,results := make(chan int, 10)收集结果 - 多路监听靠
select:select { case res := ,mutex 做不到这种组合等待 - 关闭 channel 是明确的终止信号:
close(jobs)后 range 自动退出;而用 mutex + flag 变量,你得额外处理“谁来设 flag”“设完要不要通知”这些琐事
混合使用才是常态,但边界必须清晰
真实项目里几乎不会只用一种。关键在于分工明确:channel 负责“流动”,mutex 负责“驻留”。
立即学习“go语言免费学习笔记(深入)”;
- 例如一个 HTTP 服务缓存:用
sync.RWMutex保护本地map[string][]byte,但缓存未命中时,用chan把 key 发给后台加载 goroutine,避免重复加载 - 切忌把 channel 当作“更高级的 mutex”来用——比如用
make(chan struct{}, 1)实现临界区,代码可读性暴跌,且一旦忘记close或漏收,就卡死 - 性能敏感路径(如每秒万级请求的计数器)优先 mutex;需要解耦、可测试、可扩展的业务逻辑(如异步通知、流水线处理)优先 channel
最常被忽略的一点:channel 的内存分配和调度成本是实打实的,不是“语法糖”。当你的场景里根本没有数据要传、也没有协作要编排,硬套 channel,就是在给自己埋延迟和复杂度。










