MST

星途 面试题库

面试题:Go语言并发场景下函数参数传递的隐藏陷阱与解决方案

在Go语言的并发编程中,当通过goroutine调用函数并传递参数时,可能会存在一些隐藏的陷阱,特别是涉及到共享资源和竞态条件。请详细分析这些潜在的陷阱,编写代码展示陷阱出现的场景,并提出全面且高效的解决方案。
27.6万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

潜在陷阱分析

  1. 竞态条件:多个goroutine同时访问和修改共享资源,导致最终结果不可预测。例如,多个goroutine同时对一个全局变量进行累加操作。
  2. 资源竞争:当多个goroutine竞争有限的资源(如文件描述符、数据库连接等)时,可能导致资源分配不均或资源耗尽。
  3. 闭包与共享变量:在闭包中使用共享变量时,如果多个goroutine同时调用该闭包,可能会因为共享变量的状态不一致而产生问题。

陷阱出现场景代码示例

package main

import (
    "fmt"
)

var counter int

func increment() {
    counter++
}

func main() {
    for i := 0; i < 1000; i++ {
        go increment()
    }
    fmt.Println("Final counter value:", counter)
}

在上述代码中,多个goroutine同时调用increment函数对counter进行累加。由于没有同步机制,最终的counter值往往小于1000,因为多个goroutine同时读取和修改counter,产生了竞态条件。

解决方案

  1. 互斥锁(Mutex):使用sync.Mutex来保护共享资源,确保同一时间只有一个goroutine可以访问共享资源。
package main

import (
    "fmt"
    "sync"
)

var (
    counter int
    mu      sync.Mutex
)

func increment() {
    mu.Lock()
    counter++
    mu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }
    wg.Wait()
    fmt.Println("Final counter value:", counter)
}
  1. 读写锁(RWMutex):如果对共享资源的读操作远多于写操作,可以使用sync.RWMutex。读操作可以并发进行,写操作则需要独占访问。
package main

import (
    "fmt"
    "sync"
)

var (
    data  int
    rwmu  sync.RWMutex
)

func read() int {
    rwmu.RLock()
    defer rwmu.RUnlock()
    return data
}

func write() {
    rwmu.Lock()
    data++
    rwmu.Unlock()
}
  1. 通道(Channel):通过通道进行数据传递,避免共享资源。每个goroutine处理自己的数据副本,通过通道将结果传递出去。
package main

import (
    "fmt"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Printf("Worker %d started job %d\n", id, j)
        result := j * 2
        fmt.Printf("Worker %d finished job %d with result %d\n", id, j, result)
        results <- result
    }
}

func main() {
    const numJobs = 5
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)

    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)

    for a := 1; a <= numJobs; a++ {
        <-results
    }
    close(results)
}
  1. 原子操作:对于简单的数值类型,可以使用sync/atomic包进行原子操作,避免使用锁的开销。
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

var counter int64

func increment() {
    atomic.AddInt64(&counter, 1)
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }
    wg.Wait()
    fmt.Println("Final counter value:", atomic.LoadInt64(&counter))
}