• 面试官:Go 有哪些方式安全读写共享变量


    大家好,我是木川

    Go 语言以其简洁、高效和并发性能而闻名。然而,在多个 goroutine 同时访问共享变量的情况下,可能会出现数据竞争和不确定的结果。

    为了确保数据的一致性和正确性,Go 提供了多种方式来安全读写共享变量。本文将探讨几种常见的方法,并通过示例说明它们的用法。

    一、不要修改变量

    有时候,最安全的方式就是根本不修改共享变量。sync.Once 是一个很好的工具,用于确保某个函数只被执行一次,通常用于初始化全局变量。通过 sync.Once,可以在多个 goroutine 中安全地执行初始化操作,而无需担心竞争条件。

    1. import (
    2.  "fmt"
    3.  "sync"
    4. )
    5. var sharedData int
    6. var once sync.Once
    7. func initializeSharedData() {
    8.  sharedData = 42
    9. }
    10. func main() {
    11.  for i := 0; i < 5; i++ {
    12.   once.Do(initializeSharedData)
    13.   fmt.Println(sharedData)
    14.  }
    15. }

    在上面的示例中,initializeSharedData 函数只会执行一次,确保 sharedData 只被初始化一次,而后续的 goroutine 都可以安全地读取它。

    二、只允许一个 goroutine 访问变量

    使用通道是一种防止多个 goroutine 同时访问共享变量的方法,不要通过共享变量来通信,通过通信(channel)来共享变量。

    通道是 Go 中并发编程的基础构建块,可以用于在不同 goroutine 之间传递数据,并确保同一时刻只有一个 goroutine 可以访问数据。

    1. func main() {
    2.  ch := make(chan int)
    3.  go func() {
    4.   ch <- 42 // 写入数据到通道
    5.  }()
    6.  x := <-ch // 从通道读取数据
    7.  fmt.Println(x)
    8. }

    在上面的示例中,goroutine 将数据写入通道,然后主 goroutine 从通道读取数据。这确保了数据的顺序性和一致性。

    三、允许多个 goroutine 访问变量,但是同一时间只允许一个 goroutine 访问

    如果需要允许多个 goroutine 访问变量,实现锁机制,同时只有一个线程能拿到锁,需要互斥访问,可以使用互斥锁(Mutex)、读写锁(RWMutex)或原子操作。

    sync.Mutex

    sync.Mutex 是最基本的互斥锁,它确保在任何时候只有一个 goroutine 可以访问共享变量。

    1. import (
    2.  "fmt"
    3.  "sync"
    4. )
    5. var mu sync.Mutex
    6. var sharedData int
    7. func main() {
    8.  mu.Lock()
    9.  sharedData = 42
    10.  mu.Unlock()
    11.  mu.Lock()
    12.  x := sharedData
    13.  mu.Unlock()
    14.  fmt.Println(x)
    15. }

    sync.RWMutex

    sync.RWMutex 支持多个 goroutine 同时读取共享数据,但只允许一个 goroutine 写入数据。这可以提高并发性能。

    1. import (
    2.  "fmt"
    3.  "sync"
    4. )
    5. var mu sync.RWMutex
    6. var sharedData int
    7. func main() {
    8.  mu.Lock()
    9.  sharedData = 42
    10.  mu.Unlock()
    11.  mu.RLock()
    12.  x := sharedData
    13.  mu.RUnlock()
    14.  fmt.Println(x)
    15. }

    原子操作

    sync/atomic 包提供了原子操作,可以安全地更新共享变量,这些操作是不可中断的。

    1. import (
    2.  "fmt"
    3.  "sync/atomic"
    4. )
    5. var sharedData int32
    6. func main() {
    7.  atomic.StoreInt32(&sharedData, 42// 原子存储
    8.  x := atomic.LoadInt32(&sharedData)  // 原子加载
    9.  fmt.Println(x)
    10. }

    通过上述方法,可以确保共享变量的安全读写,避免竞争条件和数据不一致性。

    总之,Go 语言提供了多种方式来确保共享变量的安全读写,可以根据具体需求选择适当的方法。无论是使用 sync.Once 防止初始化问题,使用通道进行数据传递,还是使用互斥锁、读写锁或原子操作进行同步,都能帮助你编写出并发安全的 Go 代码。

    最后给自己的原创 Go 面试小册打个广告,如果你从事 Go 相关开发,欢迎扫码购买,目前 10 元买断,加下面的微信发送支付截图额外赠送一份自己录制的 Go 面试题讲解视频

    767e907110c9b230d56d2018ecfe0c9e.jpeg

    30fe3d4e23ed87af464f8dcf077992b9.png

    如果对你有帮助,帮我点一下在看或转发,欢迎关注我的公众号

  • 相关阅读:
    离散数学 --- 特殊图 --- 偶图与平面图
    rust结构体
    ORB-SLAM2 ---- ORBmatcher::SearchByBoW函数
    【oceanbase】安装ocp,ocp部署oceanbase
    【MySQL查询常见面试题】如何避免全表扫描
    [国产MCU]-W801开发实例-WiFi网络扫描
    C++正则表达式
    笔记1.4 计算机网络性能
    谈谈什么是缓存穿透,缓存击穿,缓存雪崩?怎么解决?
    Transformer(李宏毅2022)
  • 原文地址:https://blog.csdn.net/caspar_notes/article/details/133446504