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 24 Golang
golang 如何用反射reflect操作结构体
Apr 28 Golang
解决golang结构体tag编译错误的问题
May 02 Golang
Goland使用Go Modules创建/管理项目的操作
May 06 Golang
Golang并发操作中常见的读写锁详析
Aug 30 Golang
Golang中channel的原理解读(推荐)
Oct 16 Golang
Go并发4种方法简明讲解
Apr 06 Golang
golang操作redis的客户端包有多个比如redigo、go-redis
Apr 14 Golang
Go语言入门exec的基本使用
May 20 Golang
Go 内联优化让程序员爱不释手
Jun 21 Golang
Go语言怎么使用变长参数函数
Jul 15 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操作数组的一些函数整理介绍
2011/07/17 PHP
PHP utf-8编码问题,utf8编码,数据库乱码,页面显示输出乱码
2013/04/08 PHP
探讨PHP删除文件夹的三种方法
2013/06/09 PHP
php中实现用数组妩媚地生成要执行的sql语句
2015/07/10 PHP
PHP计算当前坐标3公里内4个角落的最大最小经纬度实例
2016/02/26 PHP
PHP让数组中有相同值的组成新的数组实例
2017/12/31 PHP
JS面向对象编程 for Cookie
2010/09/19 Javascript
js自定义事件及事件交互原理概述(一)
2013/02/01 Javascript
jquery实现点击查看更多内容控制段落文字展开折叠效果
2015/08/06 Javascript
基于Bootstrap3表格插件和分页插件实例详解
2016/05/17 Javascript
深入浅出 jQuery中的事件机制
2016/08/23 Javascript
JS中页面与页面之间超链接跳转中文乱码问题的解决办法
2016/12/15 Javascript
JavaScript 网页中实现一个计算当年还剩多少时间的倒数计时程序
2017/01/25 Javascript
Vue.js实现移动端短信验证码功能
2017/03/29 Javascript
详解Vue 多级组件透传新方法provide/inject
2018/05/09 Javascript
apicloud拉起小程序并传递参数的方法示例
2018/11/21 Javascript
JavaScript对象的特性与实践应用深入详解
2018/12/30 Javascript
vue实现日历备忘录功能
2020/09/24 Javascript
vue 避免变量赋值后双向绑定的操作
2020/11/07 Javascript
Python中的各种装饰器详解
2015/04/11 Python
整理Python 常用string函数(收藏)
2016/05/30 Python
实现python版本的按任意键继续/退出
2016/09/26 Python
轻松理解Python 中的 descriptor
2017/09/15 Python
Python 利用高德地图api实现经纬度与地址的批量转换
2019/08/14 Python
python解析yaml文件过程详解
2019/08/30 Python
Pycharm小白级简单使用教程
2020/01/08 Python
Python3将ipa包中的文件按大小排序
2020/04/17 Python
Python新手学习标准库模块命名
2020/05/29 Python
浅析Python的命名空间与作用域
2020/11/25 Python
精彩的推荐信范文
2013/11/26 职场文书
大学生职业生涯规划书模版
2013/12/30 职场文书
医学求职自荐信
2014/06/21 职场文书
酒店餐厅2014重阳节活动策划方案
2014/09/16 职场文书
2015年教师节广播稿
2015/08/19 职场文书
创业者如何撰写出一份打动投资人的商业计划书?
2019/07/02 职场文书
SQLServer RANK() 排名函数的使用
2022/03/23 SQL Server