MST

星途 面试题库

面试题:Go语言匿名函数作用域与闭包结合考察

给定一段Go代码,其中包含多个匿名函数嵌套以及闭包的使用场景,请分析每个匿名函数的作用域范围,并说明可能存在的数据竞争问题及如何避免。代码如下: ```go package main import ( "fmt" ) func main() { var data []int for i := 0; i < 5; i++ { go func() { data = append(data, i) }() } fmt.Println(data) } ```
28.5万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

匿名函数作用域分析

  1. 匿名函数 func() { data = append(data, i) } 的作用域:
    • 该匿名函数在 for 循环内部定义。它可以访问到 for 循环外部的 data 变量(因为 data 定义在循环外部),同时也可以访问到 for 循环中的 i 变量。
    • 但是这里存在一个陷阱,由于Go语言中 for 循环的 i 变量是复用的,所有匿名函数捕获的实际上是同一个 i 变量。

数据竞争问题

  1. 数据竞争描述
    • 这里存在数据竞争问题。多个goroutine同时对 data 进行 append 操作,append 操作不是线程安全的,会导致数据竞争。
    • 另外,由于所有匿名函数捕获的是同一个 i 变量,当 for 循环结束后,i 的值变为 5,所以所有goroutine可能会向 data 中添加 5,而不是预期的 04

避免数据竞争的方法

  1. 使用互斥锁(Mutex)
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func main() {
        var data []int
        var mu sync.Mutex
        var wg sync.WaitGroup
        for i := 0; i < 5; i++ {
            wg.Add(1)
            go func(j int) {
                defer wg.Done()
                mu.Lock()
                data = append(data, j)
                mu.Unlock()
            }(i)
        }
        wg.Wait()
        fmt.Println(data)
    }
    
    • 在这个改进版本中,使用 sync.Mutex 来保护对 data 的操作。mu.Lock() 用于锁定互斥锁,防止其他goroutine同时修改 datamu.Unlock() 用于解锁。同时,通过将 i 作为参数传递给匿名函数,每个goroutine捕获的是不同的 i 值。
  2. 使用通道(Channel)
    package main
    
    import (
        "fmt"
    )
    
    func main() {
        dataCh := make(chan int, 5)
        var data []int
        for i := 0; i < 5; i++ {
            go func(j int) {
                dataCh <- j
            }(i)
        }
        for i := 0; i < 5; i++ {
            data = append(data, <-dataCh)
        }
        close(dataCh)
        fmt.Println(data)
    }
    
    • 在这个版本中,使用通道 dataCh 来传递数据。每个goroutine将 i 的值发送到通道中,然后在主goroutine中从通道中接收数据并添加到 data 切片中,避免了对 data 的并发访问导致的数据竞争问题。