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 26 Golang
Go语言切片前或中间插入项与内置copy()函数详解
Apr 27 Golang
golang slice元素去重操作
Apr 30 Golang
Go 自定义package包设置与导入操作
May 06 Golang
go mod 安装依赖 unkown revision问题的解决方案
May 06 Golang
Golang 语言控制并发 Goroutine的方法
Jun 30 Golang
试了下Golang实现try catch的方法
Jul 01 Golang
golang 语言中错误处理机制
Aug 30 Golang
golang生成vcf通讯录格式文件详情
Mar 25 Golang
实现GO语言对数组切片去重
Apr 20 Golang
深入理解 Golang 的字符串
May 04 Golang
Golang并发工具Singleflight
May 06 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与javascript的两种交互方式
2006/10/09 PHP
PHP初学者常见问题集合 修正版(21问答)
2010/03/23 PHP
Symfony2框架创建项目与模板设置实例详解
2016/03/17 PHP
自定义min版smarty模板引擎MinSmarty.class.php文件及用法
2016/05/20 PHP
jQuery.prototype.init选择器构造函数源码思路分析
2013/02/05 Javascript
JavaScript中window、doucment、body的解释
2013/08/14 Javascript
JavaScript 事件流、事件处理程序及事件对象总结
2017/04/01 Javascript
JavaScript数据结构中栈的应用之表达式求值问题详解
2017/04/11 Javascript
AngularJS使用ocLazyLoad实现js延迟加载
2017/07/05 Javascript
javascript ES6 新增了let命令使用介绍
2017/07/07 Javascript
jquery版轮播图效果和extend扩展
2017/07/18 jQuery
CentOS环境中MySQL修改root密码方法
2018/01/07 Javascript
微信小程序滑动选择器的实现代码
2018/08/10 Javascript
优雅地使用loading(推荐)
2019/04/20 Javascript
使用vuex解决刷新页面state数据消失的问题记录
2019/05/08 Javascript
vue中通过使用$attrs实现组件之间的数据传递功能
2019/09/01 Javascript
JavaScript设计模型Iterator实例解析
2020/01/22 Javascript
[02:27]2018DOTA2亚洲邀请赛赛前采访-OpTic
2018/04/03 DOTA
Flask入门教程实例:搭建一个静态博客
2015/03/27 Python
Django中的文件的上传的几种方式
2018/07/23 Python
python中时间模块的基本使用教程
2019/05/14 Python
Python 控制终端输出文字的实例
2019/07/12 Python
Python 类的私有属性和私有方法实例分析
2019/09/29 Python
一文解决django 2.2与mysql兼容性问题
2020/07/15 Python
Python实现Canny及Hough算法代码实例解析
2020/08/06 Python
Stefania Mode美国:奢华设计师和时尚服装
2018/01/07 全球购物
美国环保妈妈、儿童和婴儿用品购物网站:The Tot
2019/11/24 全球购物
成人大专自我鉴定范文
2013/10/19 职场文书
大学教师年终总结的自我评价
2013/10/29 职场文书
光信息科学与技术专业职业生涯规划
2014/03/13 职场文书
2014年党员自我评价材料
2014/09/22 职场文书
青年干部培训班学习心得体会
2016/01/06 职场文书
喜迎建国70周年:有关爱国的名言名句
2019/09/24 职场文书
numpy数据类型dtype转换实现
2021/04/24 Python
自从在 IDEA 中用了热部署神器 JRebel 之后,开发效率提升了 10(真棒)
2021/06/26 Java/Android
css 边框添加四个角的实现代码
2021/10/16 HTML / CSS