MST

星途 面试题库

面试题:Go语言函数参数传递与并发下的内存机制

在Go语言并发编程场景下,有多个协程调用同一个带有参数传递的函数,参数类型为自定义的结构体指针。阐述这种情况下,参数传递的内存机制以及可能会遇到的并发安全问题,并且说明如何避免这些问题。
31.7万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

参数传递的内存机制

在Go语言中,虽然函数参数传递是值传递,但传递结构体指针时,传递的是指针的值(即内存地址)。多个协程调用带有结构体指针参数的函数时,这些协程共享该结构体指针所指向的内存空间。

可能遇到的并发安全问题

  1. 数据竞争:多个协程可能同时读写该结构体的成员变量,导致数据不一致。例如,一个协程读取结构体中的某个变量,同时另一个协程修改该变量,最终读取到的值可能是不确定的。

避免问题的方法

  1. 互斥锁(Mutex): 使用sync.Mutex来保护结构体的访问。在读取或修改结构体成员变量前,先获取锁,操作完成后释放锁。
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    type MyStruct struct {
        data int
        mu   sync.Mutex
    }
    
    func modifyStruct(s *MyStruct, newData int) {
        s.mu.Lock()
        s.data = newData
        s.mu.Unlock()
    }
    
    func main() {
        var wg sync.WaitGroup
        myStruct := MyStruct{}
        for i := 0; i < 10; i++ {
            wg.Add(1)
            go func(num int) {
                defer wg.Done()
                modifyStruct(&myStruct, num)
            }(i)
        }
        wg.Wait()
        fmt.Println(myStruct.data)
    }
    
  2. 读写锁(RWMutex): 如果读操作远多于写操作,可以使用sync.RWMutex。读操作时使用读锁(RLock),写操作时使用写锁(Lock)。
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    type MyStruct struct {
        data int
        mu   sync.RWMutex
    }
    
    func readStruct(s *MyStruct) int {
        s.mu.RLock()
        defer s.mu.RUnlock()
        return s.data
    }
    
    func writeStruct(s *MyStruct, newData int) {
        s.mu.Lock()
        s.data = newData
        s.mu.Unlock()
    }
    
    func main() {
        var wg sync.WaitGroup
        myStruct := MyStruct{}
        for i := 0; i < 5; i++ {
            wg.Add(1)
            go func(num int) {
                defer wg.Done()
                writeStruct(&myStruct, num)
            }(i)
        }
        for i := 0; i < 10; i++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                fmt.Println(readStruct(&myStruct))
            }()
        }
        wg.Wait()
    }
    
  3. 通道(Channel): 可以通过通道来传递对结构体的操作请求,由一个专门的协程来处理这些请求,从而避免并发访问冲突。
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    type MyStruct struct {
        data int
    }
    
    type Op struct {
        typ  string
        data int
    }
    
    func handleOps(s *MyStruct, ops <-chan Op) {
        for op := range ops {
            switch op.typ {
            case "write":
                s.data = op.data
            case "read":
                fmt.Println(s.data)
            }
        }
    }
    
    func main() {
        var wg sync.WaitGroup
        myStruct := MyStruct{}
        opsChan := make(chan Op)
        go handleOps(&myStruct, opsChan)
        for i := 0; i < 5; i++ {
            wg.Add(1)
            go func(num int) {
                defer wg.Done()
                opsChan <- Op{"write", num}
            }(i)
        }
        for i := 0; i < 10; i++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                opsChan <- Op{"read", 0}
            }()
        }
        close(opsChan)
        wg.Wait()
    }