侧边栏壁纸
博主头像
colo

欲买桂花同载酒

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

并发环境下如何安全读写共享变量

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

题目

并发环境下如何安全读写共享变量

信息

  • 类型:问答
  • 难度:⭐

考点

并发安全,可见性,同步机制

快速回答

在Go并发编程中安全读写共享变量的核心方法:

  • 使用sync.Mutex互斥锁保护共享资源
  • 通过sync/atomic包进行原子操作
  • 遵循『不要通过共享内存来通信,而应通过通信来共享内存』原则
## 解析

原理说明

Go内存模型规定:当多个goroutine并发访问共享变量时,如果没有正确的同步机制:

  • 可能发生数据竞争(Data Race),导致未定义行为
  • 一个goroutine的写入可能对其他goroutine不可见(可见性问题)
  • 非原子操作可能被中间打断(如64位整数的读写)

代码示例

错误示例(存在数据竞争):

package main
import "fmt"

func main() {
    var counter int
    for i := 0; i < 1000; i++ {
        go func() { counter++ }() // 并发写,存在竞争
    }
    fmt.Println(counter) // 结果不确定
}

正确方案1:使用互斥锁

package main
import (
    "fmt"
    "sync"
)

func main() {
    var (
        counter int
        mu      sync.Mutex
    )

    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            mu.Lock()
            defer mu.Unlock()
            counter++
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println(counter) // 稳定输出1000
}

正确方案2:使用原子操作

package main
import (
    "fmt"
    "sync/atomic"
    "sync"
)

func main() {
    var counter int64
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            atomic.AddInt64(&counter, 1)
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println(counter) // 稳定输出1000
}

最佳实践

  • 最小化锁范围:锁的临界区应尽可能小
  • 使用defer解锁:避免忘记解锁导致死锁
  • 原子操作优先:简单计数场景首选atomic包
  • 通道替代:复杂场景考虑用channel传递数据所有权

常见错误

  • 忘记解锁互斥锁(导致死锁)
  • 复制已使用的互斥锁(sync.Mutex禁止复制)
  • 误以为单个机器字读写是原子的(32位系统上int64非原子)
  • 未使用WaitGroup等待goroutine完成

扩展知识

  • 可见性保证:sync包操作隐含内存屏障,确保修改对其他goroutine可见
  • 原子操作限制:atomic仅适用于基本类型,结构体需用mutex
  • 竞争检测:运行时加-race标志检测数据竞争(go run -race main.go)
  • Once原理:sync.Once通过原子操作+互斥锁保证单次执行