侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

实现并发安全的计数器并避免竞态条件

2025-12-8 / 0 评论 / 4 阅读

题目

实现并发安全的计数器并避免竞态条件

信息

  • 类型:问答
  • 难度:⭐⭐

考点

Goroutine同步,互斥锁使用,WaitGroup使用

快速回答

实现并发安全计数器的关键点:

  • 使用sync.Mutexsync.RWMutex保护共享状态
  • 通过sync.WaitGroup等待所有Goroutine完成
  • 避免在锁内执行耗时操作
  • 使用defer确保锁的释放
## 解析

问题场景

多个Goroutine并发操作同一个计数器时,需要保证计数操作的原子性和最终结果的正确性。

原理说明

在Go中,当多个Goroutine同时访问共享资源时会出现竞态条件(race condition)。解决方式:

  • 互斥锁(Mutex):保证同一时间只有一个Goroutine能访问临界区
  • WaitGroup:主Goroutine等待所有工作Goroutine完成
  • 原子操作:对于简单操作可使用sync/atomic

代码示例

package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    mu    sync.Mutex
    value int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

func main() {
    counter := SafeCounter{}
    var wg sync.WaitGroup

    // 启动100个Goroutine
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Println("Final count:", counter.Value()) // 正确输出100
}

最佳实践

  • 使用defer释放锁,避免忘记解锁
  • 锁的粒度要适中:过大会降低并发性,过小可能遗漏保护
  • 考虑使用RWMutex当读多写少时提高性能
  • 避免在锁内执行I/O等耗时操作

常见错误

  • 忘记释放锁导致死锁
  • 复制已使用的sync.Mutex(值传递导致锁失效)
  • 未使用WaitGroup导致主Goroutine提前退出
  • 错误地认为++操作是原子的(实际是读-改-写三步操作)

扩展知识

  • 原子操作:对于简单计数器可使用atomic.AddInt32()替代锁
  • ErrGroup:需要错误传播时使用golang.org/x/sync/errgroup
  • Channel同步:可通过channel实现计数器(但不如Mutex直观)
  • 竞态检测:编译/运行时添加-race标志检测竞态条件