分享
  1. 首页
  2. 文章

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

敬维 · · 1500 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

写在前面

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

本文通过一个例子来简单引出 Golang 中的数据并发同步问题,并通过简单加锁的方式来避免数据竞争问题。

从一个例子看线程安全与数据竞争问题

一个非常原始的数据竞争问题

下面的代码模拟了为一个用户(Person)发放金币(Money)的代码,其中金币发放与读取分别再不同的线程里完成(读取在主线程,发放在一个子线程)。

// cat main.gopackagemainimport("fmt")typePersonstruct{Moneyint}funcmain(){p:=Person{Money:100}gofunc(){p.Money+=1000}()fmt.Printf("Money: %d\n",p.Money)}

运行上面的代码:

# 只是运行上面的代码
go run main.go
# Money: 100
# 添加 -race 后检测竞争状态,可以看到 race 的提醒
go run -race main.go
# Money: 100
# ==================
# WARNING: DATA RACE
# Write at 0x00c00001c0a0 by goroutine 6:
# ....
# Previous read at 0x00c00001c0a0 by main goroutine
# ....

通过运行代码并查看输出可以确认:1 代码可以正常运行(并没有因为多个线程读写同一个变量而崩溃),2 由于数据没有同步最后输出的数据 Money: 100 与预期的数据1100存在误差,3 通过 go run -race main.go 添加 -race 标识可以发现 数据竞争 问题。

通过加锁的方式优化有数据竞争的代码

下面的代码里给 Person 添加了一个读写锁 mutext sync.RWMutex,并通过添加两个方法 GetMoneyAddMoney 来达到读取和修改 Money 的数值的目的。

packagemainimport("fmt""sync")typePersonstruct{Moneyintmutextsync.RWMutex}// GetMoney 获取用户金钱func(p*Person)GetMoney()int{p.mutext.RLock()deferp.mutext.RUnlock()money:=p.Moneyreturnmoney}// AddMoney 设置用户金钱func(p*Person)AddMoney(diffint){p.mutext.Lock()deferp.mutext.Unlock()p.Money+=diff}funcmain(){p:=Person{Money:100}gofunc(){p.AddMoney(1000)}()fmt.Printf("Money: %d\n",p.GetMoney())}

运行上面的代码:

# 添加 -race 后检测竞争状态,此时已经看不到 race 的告警
go run -race main.go
# Money: 100

通过运行代码并查看输出可以确认:1 代码可以正常运行(并没有因为多个线程读写同一个变量而崩溃),2 由于数据没有同步最后输出的数据 Money: 100 与预期的数据1100存在误差 3 已经没有了 数据竞争 问题。

示例代码的进一步阐释

上面的代码逻辑,加锁前加锁后最大的区别是:加锁前 存在数据竞争问题,加锁后不存在数据竞争问题。而无论是否加锁,代码均可以正常运行,且最终同步的数据与预期的数据均存在偏差。

之所以强调代码可以正常运行,是因为代码一定概率是会崩溃的,只是一般类型(map 类型除外)不那么容易出现崩溃的情况(任何类型变量的使用都可能会出现这个问题,详见《 谈谈go语言编程的并发安全》 和 《 benign-data-races-what-could-possibly-go-wrong 》的讨论)。

由于运行时序的存在,读取得到的数据与预期的数据存在偏差可以这样解释:虽然期望里给用户加了 1000 金钱,但是如果读取是在 加 1000 金钱 之前发生的,也确实是感知不到 加 1000 金钱 这个事件的。

小结

本文简单介绍了 Golang 中数据的并发同步问题,并通过加锁的方式避免了 数据竞争 问题。加锁在各个语言中都是一种常见的方式,理解起来是比较容易的,因此本文并没有对加锁机制进行进一步的阐述。

不过,数据的并发同步是一个涉及很广泛的问题,接下来需要继续总结。

参考


有疑问加站长微信联系(非本文作者)

本文来自:J.W.

感谢作者:敬维

查看原文:浅谈 Golang 中数据的并发同步问题(一)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1500 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏