内容简介:过去 Web 开发的工作比较少涉及到并发的问题,每个用户请求在独立的线程里面进行,偶尔涉及到异步任务但是线程间数据同步模型非常简单,因此并未深入探究过并发这一块。最近在写游戏相关的服务端代码时发现数据的并发同步场景非常多,因此花了一点时间来探索。这是一个系列文章,本文为第二篇。本文通过一个例子来引出 Golang 中的数据并发同步问题,并通过使用下面的代码模拟了为一个用户(
写在前面
过去 Web 开发的工作比较少涉及到并发的问题,每个用户请求在独立的线程里面进行,偶尔涉及到异步任务但是线程间数据同步模型非常简单,因此并未深入探究过并发这一块。最近在写游戏相关的服务端代码时发现数据的并发同步场景非常多,因此花了一点时间来探索。这是一个系列文章,本文为第二篇。
本文通过一个例子来引出 Golang 中的数据并发同步问题,并通过使用 atomic
包的方式来避免数据竞争问题。
原子性及 Go 中 atomic 包的使用
再提非常原始的数据竞争问题
下面的代码模拟了为一个用户( Person
)发放金币( Money
)的代码,其中金币发放与读取分别再不同的线程里完成(读取在主线程,发放在一个子线程)。
// cat main.go package main import ( "fmt" ) type Person struct { Money int } func main() { p := Person{Money: 100} go func() { p.Money += 1000 }() p.Money += 1000 fmt.Printf("Money: %d\n", p.Money) }
go run
时使用 -race
标志运行上面的代码可以发现数据竞争提醒:
# 添加 -race 后检测竞争状态,可以看到 race 的提醒 go run -race main.go # Money: 1100 # ================== # WARNING: DATA RACE # Write at 0x00c00001c0a0 by goroutine 6: # Previous read at 0x00c00001c0a0 by main goroutine
根据《 谈谈go语言编程的并发安全
》 和 《 benign-data-races-what-could-possibly-go-wrong
》的讨论,上面的代码无法保证 fmt.Printf("Money: %d\n", p.Money)
这一句会输出什么,同时存在严重的问题(高并发下可能出现严重问题导致代码崩溃),因此有必要通过一定的措施避免这种不确定性。
原子性概念及其实现原理
原子操作是指不会被线程调度机制打断的操作;原子操作一旦开始,就一直运行到结束,中间不会有任何 context switch(上下文切换,从当前线程切换到另一个线程)。(from 百度百科)
对于编程语言中变量的修改,可以认为原子操作需满足下面的条件:① 变量的读写过程不可中断(不可以读到一半做其他事情,也不可写到一半做其他事情),② 变量的读写过程不可同时进行(不可以写变量的同时读取变量,防止读取到仅更新了一半的变量值;也不可以写变量的同时另一个线程也在写这个变量,防止其中一个更新无效)。
根据 Golang 官方 内存模型
文档中的描述 Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access.
以及 atomic
包的存在,可以认为 Golang 对同一块内存的并发读写访问 并不是原子性
的。简而言之,Golang 中对一个变量进行更新时,另一个线程可以并发地读这个变量的值,而读取到的值是内存不确定的(即不一定读到什么东西)。
使用 atomic 包来避免数据竞争
上篇文章《 浅谈 Golang 中数据的并发同步问题(一)
》采用 添加读写锁
的方式避免了数据竞争。对于 用户
-> 金币
这种模型(金币是一个数值而非其他的复杂类型),其实也可以采用 atomic
包来避免数据竞争问题。
下面的代码引入了 atomic
的使用,由于其仅支持 int32
和 int64
这种显式定义字节长度的类型,因此代码中把 Money
修改为了 int64
类型。
package main import ( "fmt" "sync/atomic" ) type Person struct { Money int64 } func main() { p := Person{Money: 100} go func() { atomic.AddInt64(&p.Money, 1000) }() atomic.AddInt64(&p.Money, 1000) fmt.Printf("Money: %d\n", atomic.LoadInt64(&p.Money)) }
go run -race main.go
运行上面的代码可以发现数据竞争的提醒消失了:
# 添加 -race 后检测竞争状态,可以看到 race 的提醒 go run -race main.go # Money: 1100
小结
atomic
包主要局限在 数值类型
的赋值、修改、读取、交换等操作,不可以处理像 struct
、 map
这种高级类型,因此 atomic
的使用具有比较大的局限性。
就像文档中描述的: These functions (of atomics) require great care to be used correctly. Except for special, low-level applications, synchronization is better done with channels or the facilities of the sync package. Share memory by communicating; don't communicate by sharing memory.
除非知道自己在做什么,否则更推荐使用 channel
(通道) 和 sync
(锁)的方式处理数据同步问题。
参考
- 浅谈 Golang 中数据的并发同步问题(一) 系列文章的第一篇
- 原子操作_百度百科 词条,理解什么是原子操作
- 原子操作的实现原理及CAS分析 - 简书 词条
- The Go Memory Model - The Go Programming Language Golang 官方内存模型的文档
- atomic - The Go Programming Language Golang 中 atomic 包官方文档
以上所述就是小编给大家介绍的《浅谈 Golang 中数据的并发同步问题(二)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Java 并发编程:多线程并发访问,同步控制
- Java 并发编程:多线程并发访问,同步控制
- 深入Linux并发同步
- Golang 并发编程与同步原语
- 多线程六 同步容器&并发容器
- 浅谈 Golang 中数据的并发同步问题(三)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机图形学原理及实践:C语言描述(原书第2版) (平装)
福利 / 唐泽圣 / 机械工业出版社 / 2004-3 / 95.0
《计算机图形学原理及实践:C语言描述(原书第2版)》:这是计算机图形学领域的一部经典之作,作者Fley、va Dam等是国际图形学界的著名学者、学术带头人,而且《计算机图形学原理及实践:C语言描述(原书第2版)》英文版自出版以来,一直是各国大学计算机图形学课程的主要教科书。来自清华大学、北京大学、中国科学院计算技术研究所、中国科学院软件研究所的多位图形学领域的专家和精英花费了大量的时间和精力进行翻......一起来看看 《计算机图形学原理及实践:C语言描述(原书第2版) (平装)》 这本书的介绍吧!