Golang同值并发写入uint64变量是否必须用互斥锁或原子变量?
问题相关代码
type simpleTx struct { gas uint64 } func (tx *simpleTx) UpdateGas() { tx.gas = 125 } func TestUpdateGas(t *testing.T) { var wg sync.WaitGroup wg.Add(100) tx := &simpleTx{} for i:=0; i <100; i++ { go func(t *simpleTx) { tx.UpdateGas() wg.Done() }(tx) } wg.Wait() }
上述测试用例使用-race选项运行时会输出"WARNING:DATA RACE"数据竞争警告。
问题解答
Go语言中不存在支持相同值并发写入的原生类型
根据Go内存模型的定义,只要多个goroutine并发访问同一块内存地址、且至少有一个访问是写操作、同时没有同步机制约束,无论写入的值是否完全相同,都属于数据竞争,属于未定义行为,race检测器必然会触发告警。哪怕所有写入操作都是赋值同一个固定值,也不会被Go语言规范特殊对待,不会自动规避数据竞争。这类同值并发写入场景必须使用同步机制,但可选方案不止互斥锁和atomic原子变量
所有同步方案本质上都需要满足Go内存模型的可见性和原子性要求,常见适配方案如下:
- 对于简单数值类型的同值写入场景,优先用atomic原子操作:把
tx.gas的读写都替换为atomic包的对应方法,比如写入用atomic.StoreUint64(&tx.gas, 125),读取用atomic.LoadUint64(&tx.gas),开销远小于互斥锁,也能彻底消除数据竞争。 - 如果后续写入逻辑可能扩展得更复杂,可以用
sync.Mutex或sync.RWMutex包裹读写逻辑,保证临界区互斥访问。 - 如果这个值只需要赋值一次就不会再修改,也可以用
sync.Once保证全局唯一一次写入,同样能规避并发问题。
需要注意的是,哪怕你实测这种无同步的同值写入每次都能得到预期结果,也不代表程序是合规的,编译器优化、不同CPU架构的内存模型差异都可能导致意料之外的异常,必须加同步机制才能保证程序的正确性和可移植性。
内容的提问来源于stack exchange,提问作者Junhee Woo
相关产品推荐
相关产品推荐

