• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    Golang全局变量加锁的问题解决

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

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

    但是如果全局变量只有一个进程写,其他进程读呢? 如果采用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)
     }
    }

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

    原因是我们没有对全局变量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)
     }
    }

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

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

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

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

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

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

    您可能感兴趣的文章:
    • Go语言的os包中常用函数初步归纳
    • Golang 实现获取当前函数名称和文件行号等操作
    • Golang 获取文件md5校验的方法以及效率对比
    • GoLang中生成UUID唯一标识的实现
    • 聊聊golang中多个defer的执行顺序
    • go语言基础 seek光标位置os包的使用
    上一篇:golang 实现并发求和
    下一篇:聊聊golang中多个defer的执行顺序
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

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