Golang代码中常见的并发问题有哪些
Golang代码中常见的并发问题有哪些 在代码审查或现有代码和库中,你反复注意到哪些与并发相关的代码或设计层面的问题?
我注意到的一些情况:
time.Ticker未被停止:缺少defer tick.Stop()- 一个类型被设计为可供多个goroutine并发使用是安全的,但其文档没有说明哪些方法可以从哪些goroutine以及在何时调用。(默认情况可能是“任何方法都可以在任何时刻从任何goroutine调用”,但对于许多类型来说,这必须更具体,例如,某些方法必须仅从单个goroutine顺序调用。)
- 与上一点相关/重叠:结构体中有一个
sync.Mutex字段,但没有注释说明它应该保护哪些字段(特别是当字段超过1-2个,并且哪些是可变的、哪些是不可变的并不明显)以及原因。
(此帖转载自 Reddit。)
更多关于Golang代码中常见的并发问题有哪些的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于Golang代码中常见的并发问题有哪些的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Golang并发编程中,确实存在一些常见问题。以下是我在实际代码审查中经常遇到的情况:
1. 竞态条件(Race Conditions)
最常见的问题是未使用同步机制保护共享数据:
// 错误示例
var counter int
func increment() {
counter++ // 存在竞态条件
}
// 正确示例
var (
counter int
mu sync.RWMutex
)
func increment() {
mu.Lock()
defer mu.Unlock()
counter++
}
2. 通道使用不当
包括通道未关闭、阻塞操作等:
// 错误示例 - 通道泄漏
func process(ch chan int) {
for i := 0; i < 10; i++ {
go func(val int) {
ch <- val * 2
}(i)
}
// 通道从未关闭
}
// 正确示例
func process(ch chan int) {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(val int) {
defer wg.Done()
ch <- val * 2
}(i)
}
go func() {
wg.Wait()
close(ch)
}()
}
3. 资源泄漏
如你提到的 time.Ticker 问题:
// 错误示例
func startTicker() {
ticker := time.NewTicker(time.Second)
go func() {
for range ticker.C {
// 处理逻辑
}
}()
// ticker 从未停止
}
// 正确示例
func startTicker() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop() // 确保停止
go func() {
for range ticker.C {
// 处理逻辑
}
}()
}
4. 同步原语误用
sync.WaitGroup 和 sync.Mutex 的常见错误:
// 错误示例 - WaitGroup 使用不当
func process() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
go func() {
wg.Add(1) // 在 goroutine 中调用 Add
defer wg.Done()
// 工作
}()
}
wg.Wait() // 可能过早返回
}
// 正确示例
func process() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1) // 在 goroutine 外调用 Add
go func() {
defer wg.Done()
// 工作
}()
}
wg.Wait()
}
5. 上下文(Context)传播问题
未正确传递或取消上下文:
// 错误示例
func handleRequest(ctx context.Context) {
go func() {
// 使用了外部上下文,但未处理取消
result := longRunningTask(ctx)
// ...
}()
// 函数返回后,goroutine 可能仍在运行
}
// 正确示例
func handleRequest(ctx context.Context) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
done := make(chan struct{})
go func() {
defer close(done)
result := longRunningTask(ctx)
// ...
}()
select {
case <-ctx.Done():
// 处理取消
case <-done:
// 正常完成
}
}
6. 死锁
通常由锁的顺序问题引起:
// 错误示例 - 潜在的锁顺序死锁
type Service struct {
mu1 sync.Mutex
mu2 sync.Mutex
data1 map[string]int
data2 map[string]int
}
func (s *Service) Process() {
s.mu1.Lock()
s.mu2.Lock() // 如果另一个 goroutine 以相反顺序获取锁,可能死锁
// ...
s.mu2.Unlock()
s.mu1.Unlock()
}
// 正确示例 - 使用一致的锁顺序
func (s *Service) Process() {
// 总是先获取 mu1,再获取 mu2
s.mu1.Lock()
defer s.mu1.Unlock()
s.mu2.Lock()
defer s.mu2.Unlock()
// ...
}
7. 原子操作误用
误用 sync/atomic 包:
// 错误示例
var config atomic.Value
func updateConfig() {
cfg := &Config{Field: "value"}
config.Store(cfg) // 存储指针,后续修改会影响已存储的值
cfg.Field = "modified" // 这会影响已存储的配置
}
// 正确示例
func updateConfig() {
cfg := &Config{Field: "value"}
// 创建副本或确保不再修改
config.Store(cfg)
// 不再修改 cfg
}
这些问题通常可以通过以下方式避免:
- 使用
go test -race进行竞态检测 - 使用
sync包提供的同步原语 - 明确文档记录并发安全要求
- 使用
context进行生命周期管理 - 对共享数据进行适当的封装和保护

