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

栏目: Go · 发布时间: 6年前

内容简介:过去 Web 开发的工作比较少涉及到并发的问题,每个用户请求在独立的线程里面进行,偶尔涉及到异步任务但是线程间数据同步模型非常简单,因此并未深入探究过并发这一块。最近在写游戏相关的服务端代码时发现数据的并发同步场景非常多,因此花了一点时间来探索。文本通过一个例子来简单引出 Golang 中的数据并发同步问题,并通过简单加锁的方式来避免数据竞争问题。下面的代码模拟了为一个用户(

写在前面

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

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

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

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

下面的代码模拟了为一个用户( 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
	}()
	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
# ....

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

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

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

package main

import (
	"fmt"
	"sync"
)

type Person struct {
	Money  int
	mutext sync.RWMutex
}

// GetMoney 获取用户金钱
func (p *Person) GetMoney() int {
	p.mutext.RLock()
	defer p.mutext.RUnlock()
	money := p.Money
	return money
}

// AddMoney 设置用户金钱
func (p *Person) AddMoney(diff int) {
	p.mutext.Lock()
	defer p.mutext.Unlock()
	p.Money += diff
}

func main() {
	p := Person{Money: 100}
	go func() {
		p.AddMoney(1000)
	}()
	fmt.Printf("Money: %d\n", p.GetMoney())
}

运行上面的代码:

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

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

示例代码的进一步阐释

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

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

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

小结

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

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

参考


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

另一个地球

另一个地球

[美]马克·格雷厄姆、威廉·H·达顿 / 胡泳、徐嫩羽 / 电子工业出版社 / 2015-10-1 / 78

互联网在日常工作和生活中扮演日益重要的角色,互联网将如何重塑社会?本书通过汇集有关互联网文化、经济、政治角色等问题的研究成果,提供了特定社会制度背景下解决这一问题的根本办法。 关于互联网的研究是蓬勃发展的崭新领域,牛津大学互联网研究院(OII)作为创新型的跨学科学院,自成立起就专注于互联网研究。牛津大学互联网研究院关于互联网+社会的系列讲座在一定程度上塑造了互联网+社会。本书内容基于不同学科......一起来看看 《另一个地球》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

SHA 加密
SHA 加密

SHA 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具