应优先用 channel 通信而非共享内存;解决“数据流动”用 channel,解决“状态保护”用 sync.mutex;非缓冲 channel 读写默认双向阻塞。

Go 语言里协程(goroutine)之间不该直接共享内存来传递数据,而应优先用 channel 通信;只有在明确需要“多个 goroutine 协作修改同一块状态”时,才考虑 sync.Mutex 或原子操作。
什么时候该用 channel 而不是锁
核心判断标准是:你在解决“数据流动”问题,还是“状态保护”问题。
- 用
channel:生产者-消费者、任务分发、事件通知、扇入/扇出、协程生命周期同步(比如等待所有 goroutine 完成) - 用
sync.Mutex:缓存更新、计数器累加、配置热重载、状态机迁移等需要多 goroutine 共同读写同一变量的场景 - 常见误用:为一个简单计数器开 10 个 goroutine 去
counter++,却用channel每次传一个1再累加——这引入了不必要的调度开销和 channel 创建成本
channel 的阻塞行为直接影响逻辑走向
非缓冲 channel(make(chan int))的读写操作默认是双向阻塞的:发送方会卡在 ch 直到有接收方准备就绪;接收方会卡在 <code> 直到有数据可取。这个特性天然适合做同步,但也很容易导致死锁。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 死锁典型场景:
go func() { ch 后立刻 <code>close(ch),但没 goroutine 接收 → 程序 panic: send on closed channel - 避免死锁:要么确保有接收方在运行(如用
range ch),要么用带缓冲 channel(make(chan int, 1))或select+default做非阻塞尝试 - 关闭 channel 的责任应在发送方:只读 channel(
)无法 close,close 未关闭的 channel 会 panic
共享内存加锁的性能与语义边界
锁不是洪水猛兽,它在特定场景下比 channel 更轻量、更可控。
- 高频小粒度修改(如每秒万次的计数器更新):用
atomic.AddInt64比走 channel 快一个数量级,且无 goroutine 调度开销 - 读多写少场景:用
sync.RWMutex可让并发读不互斥,比 channel 的串行化吞吐高得多 - 锁的粒度要细:别对整个结构体加一把大锁,而是按字段或业务域拆分,否则会成为瓶颈
- 注意:锁只保临界区安全,不保逻辑正确性。比如两个 goroutine 分别调用
cache.Get(k)和cache.Set(k, v),即使各自加锁,仍可能出现脏读——这得靠更高层协议(如 singleflight)解决
真正容易被忽略的不是语法,而是设计意图:channel 是为了表达“谁在提供数据、谁在消费数据”,而锁是为了表达“这块内存此刻归谁管”。混用两者本身没问题,但混淆它们的职责,就会写出既难 debug 又难优化的并发代码。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










