Skip to content

并发安全

  • 并发安全指的是多个 goroutine 同时访问共享资源时,不会出现数据竞争、逻辑错误或不一致的结果。

数据竞争的本质

  • 数据竞争是指多个 goroutine 同时访问共享资源,且至少有一个 goroutine 对该资源进行写操作。
  • 数据竞争会导致不可预测的结果,例如数据损坏、不一致的状态等。

解决方案

互斥锁 (Mutex)

  • 排他锁,同一时间只允许一个 goroutine 访问资源(读 / 写互斥)。
go
var mu sync.Mutex
var count int

func main() {
    defer mu.Unlock()
    // 上锁
	mu.Lock()
	count++
}

读写锁 (RWMutex)

  • 读写锁,允许多个读操作并发,但写操作会阻塞所有读写。适合「读多写少」场景。
go
var rwMu sync.RWMutex
var data map[string]int

func readKey(key string) int {
	rwMu.RLock() // 读锁
	defer rwMu.RUnlock()
	return data[key]
}

func writeKey(key string, val int) {
	rwMu.Lock() // 写锁
	defer rwMu.Unlock()
	data[key] = val
}

原子操作 (Atomic)

  • 针对基本数据类型(如 int32、uint64 等)的简单操作,可使用原子函数避免锁的开销。
go
var counter int64

func add() {
    atomic.AddInt64(&counter, 1) // 原子自增
}

func get() int64 {
    return atomic.LoadInt64(&counter) // 原子读取
}

通道

  • 天然并发安全,无需显式锁。
go
var ch = make(chan int, 1) // 带缓冲的 channel 可减少阻塞

// 发送方
go func() {
    ch <- 100 // 数据通过 channel 传递
}()

// 接收方
go func() {
    val := <-ch // 安全获取数据
}()

等待组

  • 等待组,用于等待多个 goroutine 完成,防止主程序提前退出。
go
var wg sync.WaitGroup

func task() {
    defer wg.Done() // 任务完成时减 1
    // 业务逻辑
}

func main() {
    wg.Add(5) // 启动 5 个任务
    for i := 0; i < 5; i++ {
        go task()
    }
    wg.Wait() // 等待所有任务完成
}

sync.Once

  • 确保某个操作只执行一次,常用于初始化操作。
  • 示例:初始化数据库连接、加载配置文件、单例模式。
go
var once sync.Once
once.Do(func() {
    // 初始化操作,只执行一次
})

常见问题

  • 死锁:多个 goroutine 等待对方释放锁,导致程序无法继续执行。
  • 活锁:多个 goroutine 不断切换执行状态,导致程序无法继续执行。
  • 饥饿:某个 goroutine 长期无法获取到锁,导致其他 goroutine 一直等待。