Golang全局变量加锁的问题解决


Posted in Golang onMay 08, 2021

如果全局变量只读取 那自然是不需要加锁的

如果全局变量多进程读,多进程写,那自然是需要加读写锁的

但是如果全局变量只有一个进程写,其他进程读呢? 如果采用COW的方式,写进程只是通过单次赋值的方式来更新变量,是否就可以不加锁了呢?

就第三种情况而言:

当然我们通过 go build -race 或者 go run -race 就会出现

WARNING: DATA RACE。 但是出现 data race 就证明一定有问题么?

其实核心点在于这个赋值是否是原子的。也就是说是否存在 p1 = p2 的写入时,指针被临时替换为p3,并在此时goroutine切出的情况。可以想到的一种情况是64字节的指针需要两次mv才能完成全部变量的赋值。那么就有可能在两次mv中间切出,进而出现p3的情况。

之前在stackoverflow 上有个讨论

https://stackoverflow.com/questions/21447463/is-assigning-a-pointer-atomic-in-go

其中高votes的回答是说:

在go中,唯一保证原子性的操作是在 sync.atomic, 所以如果你想确保原子性,可以使用sync.Mutex 或者 sync.atomic 中的原子函数。 但是我不建议 sync.atomic中函数, 因为你不得不在任何使用指针的地方使用他们,这是非常难做到正确使用的。

用mutex 是好的go style - 你可以很方便的定义一个函数返回指针。 比如

import "sync"
var secretPointer *int
var pointerLock sync.Mutex
func CurrentPointer() *int {
    pointerLock.Lock()
    defer pointerLock.Unlock()
    return secretPointer
}
func SetPointer(p *int) {
    pointerLock.Lock()
    secretPointer = p
    pointerLock.Unlock()
}

所以一个ok的go style 应该是使用 sync.Mutex 的。

golang doc也是这么说的。

type T struct {
 msg string
}
var g *T
func setup() {
 t := new(T)
 t.msg = "hello, world"
 g = t
}
func main() {
 go setup()
 for g == nil {
 }
 print(g.msg)
}

Even if main observes g != nil and exits its loop, there is no guarantee that it will observe the initialized value for g.msg.

In all these examples, the solution is the same: use explicit synchronization.

但是当我们用go tool asm看时, 确实只有一个指令 MOVQ。

所以只能说

因为规范没有指定,所以你应该假设它不是原子的。即使它现在是原子的,它也有可能在不违反规范的情况下改变。

总之我们不应该做赋值原子的假设,而应该按照规范,使用sync.Mutex 来做。

补充:Golang对全局变量加锁同步解决资源访问共享问题——使用Go协程来同时并发计算多个数字(1-200)的阶乘

使用互斥锁解决资源共享问题

使用Go协程来同时并发计算多个数字(1-200)的阶乘,然后存储在数组当中

package main 
import (
 "fmt"
 "time"
)
 
var(
 myMap = make(map[int]int, 10)
)
 
func test(n int){
 res:=1
 for i:=1; i<=n; i++{
  res*=i
 }
 myMap[n]=res
}
 
func main(){
 for i:=1; i<=200; i++{
  go test(i)
 }
 
 time.Sleep(time.Second*10) 
 for i,v:=range myMap{
  fmt.Printf("myMap[%d]=%d\n", i, v)
 }
}

代码如下,运行结果如下:但是我们发现其并没有正常计算出各个数字的阶乘来

Golang全局变量加锁的问题解决

原因是我们没有对全局变量myMap加锁,导致了资源抢夺的问题,因此我们可以对代码加入互斥锁

package main 
import (
 "fmt"
 "time"
 "sync"
)
 
var(
 myMap = make(map[int]int, 10)
 //声明一个全局互斥锁
 lock sync.Mutex
)
 
func test(n int){
 res:=1
 for i:=1; i<=n; i++{
  res+=i //这里我将阶乘改成求和,防止数据溢出
 }
 //加锁
 lock.Lock()
 myMap[n]=res
 //解锁
 lock.Unlock()
}
 
func main(){
 for i:=1; i<=200; i++{
  go test(i)
 }
 
 time.Sleep(time.Second*10)
 
 for i,v:=range myMap{
  fmt.Printf("myMap[%d]=%d\n", i, v)
 }
}

对资源加了互斥锁之后,多个协程之间的并发问题就得到了解决

Golang全局变量加锁的问题解决

但是上述解决方案不太完美,有其缺陷:

(1)主线程在等待所有goroutine全部完成的时间很难确定

(2)如果主线程休眠时间过长,就会加长等待时间,如果等待时间短了,还可能会有goroutine因为主线程的退出而被销毁

(3)通过全局变量加锁同步来实现通讯,也不利于多个协程对全局变量的读写操作

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
Go各时间字符串使用解析
Apr 02 Golang
golang http使用踩过的坑与填坑指南
Apr 27 Golang
golang在GRPC中设置client的超时时间
Apr 27 Golang
Go 自定义package包设置与导入操作
May 06 Golang
基于Golang 高并发问题的解决方案
May 08 Golang
go xorm框架的使用
May 22 Golang
Go语言空白表示符_的实例用法
Jul 04 Golang
Go语言基础函数基本用法及示例详解
Nov 17 Golang
Go语言特点及基本数据类型使用详解
Mar 21 Golang
golang生成并解析JSON
Apr 14 Golang
Go语言入门exec的基本使用
May 20 Golang
GoFrame gredis缓存DoVar Conn连接对象 自动序列化GoFrame gredisDo/DoVar方法Conn连接对象自动序列化/反序列化总结
Jun 14 Golang
golang 实现并发求和
May 08 #Golang
golang中的并发和并行
May 08 #Golang
关于golang高并发的实现与注意事项说明
May 08 #Golang
基于Golang 高并发问题的解决方案
May 08 #Golang
使用golang编写一个并发工作队列
May 08 #Golang
Go 在 MongoDB 中常用查询与修改的操作
May 07 #Golang
golang 实现时间戳和时间的转化
May 07 #Golang
You might like
php分页示例代码
2007/03/19 PHP
基于ubuntu下nginx+php+mysql安装配置的具体操作步骤
2013/04/28 PHP
PHP生成唯一订单号
2015/07/05 PHP
PHP使用第三方即时获取物流动态实例详解
2017/04/27 PHP
JSON序列化与解析原生JS方法且IE6和chrome测试通过
2013/09/05 Javascript
jquery下div 的resize事件示例代码
2014/03/09 Javascript
jquery代码实现简单的随机图片瀑布流效果
2015/04/20 Javascript
比例尺、缩略图、平移缩放之百度地图添加控件方法
2015/08/03 Javascript
jQuery height()、innerHeight()、outerHeight()函数的区别详解
2016/05/23 Javascript
jQuery中slidedown与slideup方法用法示例
2016/09/16 Javascript
谈谈JavaScript中浏览器兼容问题的写法小议
2016/12/17 Javascript
Vue2.0组件间数据传递示例
2017/03/07 Javascript
Vue指令之 v-cloak、v-text、v-html实例详解
2019/08/08 Javascript
JavaScript字符串处理常见操作方法小结
2019/11/15 Javascript
微信小程序仿抖音视频之整屏上下切换功能的实现代码
2020/05/24 Javascript
JS 5种遍历对象的方式
2020/06/16 Javascript
[00:34]DOTA2上海特级锦标赛 VG战队宣传片
2016/03/04 DOTA
[02:49:21]2019完美盛典全程录像
2019/12/08 DOTA
python 获取et和excel的版本号
2009/04/09 Python
用Python实现一个简单的线程池
2015/04/07 Python
解决python爬虫中有中文的url问题
2018/05/11 Python
python实现txt文件格式转换为arff格式
2018/05/31 Python
解决tensorflow测试模型时NotFoundError错误的问题
2018/07/26 Python
python list数据等间隔抽取并新建list存储的例子
2019/11/27 Python
css3的过滤效果简单实例
2016/08/03 HTML / CSS
国外最大的眼镜网站:Coastal
2017/08/09 全球购物
英国剑桥包中文官网:The Cambridge Satchel Company中国
2018/11/06 全球购物
促销活动方案模板
2014/02/24 职场文书
三关爱志愿服务活动方案
2014/08/17 职场文书
党员个人剖析材料
2014/09/30 职场文书
买卖合同协议书范本
2014/10/18 职场文书
给朋友的道歉短信
2015/05/12 职场文书
运动会闭幕式通讯稿
2015/07/18 职场文书
JavaScript实现简单计时器
2021/06/22 Javascript
vue+element ui实现锚点定位
2021/06/29 Vue.js
Nginx禁止ip访问或非法域名访问
2022/04/07 Servers