0

0

如何避免Go并发中的数据竞争_Go并发安全处理方法

P粉602998670

P粉602998670

发布时间:2026-01-13 11:03:52

|

280人浏览过

|

来源于php中文网

原创

应使用 sync.Mutex 保护共享变量,因 goroutine 轻量但非线程安全;多 goroutine 同时读写未同步变量会引发数据竞争,需读写均加锁,避免漏锁、死锁或锁粒度不当。

如何避免go并发中的数据竞争_go并发安全处理方法

sync.Mutex 保护共享变量,而不是靠“我保证不会同时写”

Go 的 goroutine 轻量,但不等于线程安全。只要多个 goroutine 同时读写同一个变量(比如一个全局 map 或结构体字段),且没加同步机制,就构成数据竞争——go run -race 会直接报 Data race 错误。

常见错误是只对写操作加锁、读操作裸奔,或者锁粒度太粗(整个函数一把锁)拖慢性能,太细(每行都 lock/unlock)又容易漏锁或死锁。

  • 读写都必须在 mutex.Lock()mutex.Unlock() 之间完成
  • 避免在锁内调用可能阻塞或重入的函数(如另一个带锁函数)
  • 优先使用 defer mu.Unlock(),防止忘记解锁
  • 如果只是读多写少,考虑 sync.RWMutex:多个 goroutine 可并发 RLock(),但写必须独占 Lock()
var mu sync.Mutex
var counter int

func increment() { mu.Lock() defer mu.Unlock() counter++ }

sync/atomic 替代锁处理简单整数或指针操作

当共享变量只是 int32int64uint32uintptr 或指针,且操作是原子读、写、增减、比较并交换(CAS),sync/atomicMutex 更轻量、无锁、性能更高。

注意:atomic 不适用于结构体、浮点数(除非转成 uint64 再操作)、或需要多步协调的逻辑(比如“先读再判断再写”这种非原子组合)。

  • atomic.AddInt64(&x, 1) 是安全的;x++ 不是
  • atomic.LoadInt64(&x)atomic.StoreInt64(&x, v) 用于读写
  • atomic.CompareAndSwapInt64(&x, old, new) 是实现无锁队列、状态机的基础
  • 所有 atomic 函数参数必须是指针,且变量必须是导出的(首字母大写)或全局对齐的,否则运行时 panic
var ops uint64

func worker() { for i := 0; i < 100; i++ { atomic.AddUint64(&ops, 1) } }

用 channel 替代共享内存,让 goroutine 通过通信来同步

Go 的哲学是 “不要通过共享内存来通信,而应通过通信来共享内存”。意思是:与其大家抢着读写一个变量,不如让一个 goroutine 独占该变量,其他 goroutine 通过 channel 发送指令(如 “加1”、“取值”)给它,由它串行处理。

Viggle AI
Viggle AI

Viggle AI是一个AI驱动的3D动画生成平台,可以帮助用户创建可控角色的3D动画视频。

下载

这天然规避了数据竞争,也更易推理。但要注意 channel 容量和阻塞行为——无缓冲 channel 会同步等待收发双方就绪;有缓冲 channel 可能掩盖背压问题。

  • 适合封装状态机、计数器、连接池等有明确 owner 的资源
  • 避免在 select 中对同一 channel 多次收发导致逻辑混乱
  • 记得关闭 channel 并让接收方检测 ok,否则可能死锁或 panic
  • 别把 channel 当作通用消息总线滥用;复杂交互建议用 sync.WaitGroup + 明确生命周期控制
type Counter struct {
    ops chan int64
}

func NewCounter() *Counter { c := &Counter{ops: make(chan int64)} go c.run() return c }

func (c *Counter) run() { var total int64 for inc := range c.ops { total += inc } }

func (c *Counter) Inc(n int64) { c.ops <- n }

启用 -race 检测器,但别只依赖它发现所有竞争

go run -race main.gogo test -race 是 Go 自带的数据竞争检测器,基于动态插桩,在运行时捕获大部分竞态访问。但它不是万能的:

  • 只能检测实际执行到的竞争路径;未触发的竞态不会报
  • 对低概率竞争(如每万次运行才一次)可能漏检
  • 无法检测逻辑错误(比如两个 goroutine 都正确加锁,但业务上不该同时执行)
  • 开启后程序变慢、内存占用高,不能长期在线上启用

真正可靠的策略是:设计阶段就决定谁 owns 哪块数据,用 mutex / atomic / channel 显式约束访问路径;-race 只是上线前最后一道验证。

最容易被忽略的是:第三方库内部也可能有数据竞争,尤其是手动管理内存或复用对象池(sync.Pool)时——务必检查其文档是否声明并发安全,必要时加隔离 wrapper。

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

194

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

187

2025.07.04

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

480

2023.08.10

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

74

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

28

2025.11.16

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

59

2025.11.17

java判断map相关教程
java判断map相关教程

本专题整合了java判断map相关教程,阅读专题下面的文章了解更多详细内容。

35

2025.11.27

Golang channel原理
Golang channel原理

本专题整合了Golang channel通信相关介绍,阅读专题下面的文章了解更多详细内容。

244

2025.11.14

jQuery 正则表达式相关教程
jQuery 正则表达式相关教程

本专题整合了jQuery正则表达式相关教程大全,阅读专题下面的文章了解更多详细内容。

1

2026.01.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 3.6万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号