浅谈 Golang 中数据的并发同步问题(二)

北京/设计爱好者/3年前/94浏览
浅谈 Golang 中数据的并发同步问题(二)
dasein58

浅谈 Golang 中数据的并发同步问题(二)

  过去 Web 开发的工作比较少涉及到并发的问题,每个用户请求在独立的线程里面进行,偶尔涉及到异步任务但是线程间数据同步模型非常简单,因此并未深入探究过并发这一块。最近在写游戏相关的服务端代码时发现数据的并发同步场景非常多,因此花了一点时间来探索。这是一个系列文章,本文为第二篇。

  本文通过一个例子来引出 Golang 中的数据并发同步问题,并通过使用 atomic 包的方式来避免数据竞争问题。

  下面的代码模拟了为一个用户(Person)发放金币(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

  ", p.Money) 这一句会输出什么,同时存在严重的问题(高并发下可能出现严重问题导致代码崩溃),因此有必要通过一定的措施避免这种不确定性。

  原子操作是指不会被线程调度机制打断的操作;原子操作一旦开始,就一直运行到结束,中间不会有任何 context switch(上下文切换,从当前线程切换到另一个线程)。(from 百度百科)

  对于编程语言中变量的修改,可以认为原子操作需满足下面的条件:①古玩交易变量的读写过程不可中断(不可以读到一半做其他事情,也不可写到一半做其他事情),② 变量的读写过程不可同时进行(不可以写变量的同时读取变量,防止读取到仅更新了一半的变量值;也不可以写变量的同时另一个线程也在写这个变量,防止其中一个更新无效)。

  根据 Golang 官方 内存模型 文档中的描述 Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access. 以及 atomic 包的存在,可以认为 Golang 对同一块内存的并发读写访问并不是原子性的。简而言之,Golang 中对一个变量进行更新时,另一个线程可以并发地读这个变量的值,而读取到的值是内存不确定的(即不一定读到什么东西)。

  上篇文章浅谈 Golang 中数据的并发同步问题(一)采用 添加读写锁 的方式避免了数据竞争。对于 用户->金币 这种模型(金币是一个数值而非其他的复杂类型),其实也可以采用 atomic 包来避免数据竞争问题。

  下面的代码引入了 atomic 的使用,由于其仅支持 int32 和 int64 这种显式定义字节长度的类型,因此代码中把 Money 修改为了 int64 类型。

  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 包官方文档


0
阅读原文
|
Report
|
收藏
Share
相关推荐
评论
in to comment
Add emoji
喜欢TA的作品吗?喜欢就快来夸夸TA吧!
推荐素材
You may like
8月的“话”
Homepage recommendation
相关收藏夹
ip形象设计+表情包
ip形象设计+表情包
ip形象设计+表情包
ip形象设计+表情包
精选收藏夹
作品收藏夹
企业展厅/文化墙 参考
企业展厅/文化墙 参考
企业展厅/文化墙 参考
企业展厅/文化墙 参考
精选收藏夹
作品收藏夹
小家电
小家电
小家电
小家电
精选收藏夹
作品收藏夹
企业展厅
企业展厅
企业展厅
企业展厅
精选收藏夹
作品收藏夹
IP形象及IP内容
IP形象及IP内容
IP形象及IP内容
IP形象及IP内容
精选收藏夹
作品收藏夹
IP形象——动物类
IP形象——动物类
IP形象——动物类
IP形象——动物类
精选收藏夹
作品收藏夹
大家都在看
Log in