设计思路
- 减少不必要的数据传递:
- 只在 context 中传递真正需要在不同 goroutine 间共享且会影响业务逻辑的数据。避免传递大的结构体或频繁变化但对业务流程非关键的数据。例如,如果某些数据只是在特定 goroutine 内部使用,就不要放入 context 中。
- 数据结构优化:
- 对于放入 context 的数据,选择合适的数据结构。如果数据是只读的,可使用不可变数据结构,这样天然避免数据竞争。比如使用 Go 语言的
sync.Map
来存储只读的键值对数据,它在高并发读操作下性能较好。对于需要读写的数据,考虑使用线程安全的数据结构,如 sync.RWMutex
保护的 map 或者使用 channel 来进行数据传递和同步。
- 分层设计:
- 将 context 数据传递分为不同层次。例如,顶层 context 只传递全局性、贯穿整个业务流程的少量关键数据,如请求的唯一标识等。在具体的业务模块内部,根据需要创建子 context,并在子 context 中传递该模块内所需的数据,这样可以减少数据传递的范围和复杂度。
技术手段
- 使用 context.WithValue 优化:
- context.WithValue 用于向 context 中传递数据,但要注意不要滥用。在创建 context 时,尽量减少传递的数据量。并且对于通过 context.WithValue 传递的数据,确保其类型是简单且线程安全的。如果传递自定义结构体,要保证结构体内部的字段访问是线程安全的。
- 利用 context 取消机制:
- 合理利用 context 的取消机制来控制 goroutine 的生命周期。在不需要某些 goroutine 继续执行时,及时取消它们,避免无用的计算和数据传递。例如,在一个包含多个并发操作的请求处理中,如果用户取消了请求,通过 context 取消机制快速停止所有相关的 goroutine,防止它们继续处理和传递数据。
- 使用 sync 包的工具:
- 对于 context 中传递的需要读写的数据,使用
sync.Mutex
或 sync.RWMutex
进行保护。例如:
type ContextData struct {
mu sync.RWMutex
value int
}
func (cd *ContextData) GetValue() int {
cd.mu.RLock()
defer cd.mu.RUnlock()
return cd.value
}
func (cd *ContextData) SetValue(v int) {
cd.mu.Lock()
defer cd.mu.Unlock()
cd.value = v
}
- Channel 辅助:
- 可以使用 channel 来辅助传递和同步 context 中的数据。例如,将需要在不同 goroutine 间共享的数据通过 channel 发送和接收,这样利用 channel 的特性(如自动同步和缓冲)来避免数据竞争。在需要将 context 中的数据传递给新启动的 goroutine 时,通过 channel 将数据发送给该 goroutine,而不是直接在 context 中传递复杂的数据结构。