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

 更新时间:2021年05月08日 11:57:43   作者:hongweikkx  
这篇文章主要介绍了解决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编程库Sync.Pool用法示例详解

    Go编程库Sync.Pool用法示例详解

    这篇文章主要为大家介绍了Go编程库Sync.Pool用法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • 使用Go语言与MQTT进行通信的示例代码

    使用Go语言与MQTT进行通信的示例代码

    本文介绍了如何使用 Go 编程语言与 MQTT(Message Queuing Telemetry Transport)进行通信,MQTT 是一种轻量级的消息传输协议,广泛应用于物联网和实时通信场景,通过本文的指导,您将学习如何使用 Go 语言创建 MQTT 客户端,进行消息的发布和订阅,需要的朋友可以参考下
    2023-12-12
  • go语言context包功能及操作使用详解

    go语言context包功能及操作使用详解

    这篇文章主要为大家介绍了go语言context包功能及操作使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04
  • go语言使用RC4加密的方法

    go语言使用RC4加密的方法

    这篇文章主要介绍了go语言使用RC4加密的方法,实例分析了RC4加密的技巧与实现方法,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-03-03
  • go语言编程二维码生成及识别

    go语言编程二维码生成及识别

    这篇文章主要为大家介绍了go语言编程二维码的生成及识别示例演示,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-04-04
  • Golang中基于HTTP协议的网络服务

    Golang中基于HTTP协议的网络服务

    HTTP协议是基于TCP/IP协议栈的,并且它也是一个面向普通文本的协议。这篇文章主要详细介绍了Golang中基于HTTP协议的网络服务,感兴趣的小伙伴可以借鉴一下
    2023-04-04
  • 浅谈Go语言多态的实现与interface使用

    浅谈Go语言多态的实现与interface使用

    如果大家系统的学过C++、Java等语言以及面向对象的话,相信应该对多态不会陌生。多态是面向对象范畴当中经常使用并且非常好用的一个功能,它主要是用在强类型语言当中,像是Python这样的弱类型语言,变量的类型可以随意变化,也没有任何限制,其实区别不是很大
    2021-06-06
  • 通过函数如何将golang float64 保留2位小数(方法汇总)

    通过函数如何将golang float64 保留2位小数(方法汇总)

    这篇文章主要介绍了通过函数将golang float64保留2位小数,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-08-08
  • Golang实现协程超时控制的方式总结

    Golang实现协程超时控制的方式总结

    我们知道,go协程如果不做好处理,很容易造成内存泄漏,所以对goroutine做超时控制,才能有效避免这种情况发生,本文为大家整理了两个常见的Golang超时控制方法,需要的可以收藏一下
    2023-05-05
  • Go语言读取文件的四种方式

    Go语言读取文件的四种方式

    本文主要介绍了Go语言读取文件的四种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04

最新评论