MST

星途 面试题库

面试题:Go闭包在并发场景下的表现

在Go语言并发编程中,使用闭包作为goroutine的执行函数,例如:var num int = 0 for i := 0; i < 10; i++ { go func() { num++ }() } 最后输出num的值可能是多少?为什么?如何确保得到预期的结果?这里涉及到闭包在并发环境下的哪些特性?
16.9万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 最后输出num的值可能是多少?
    • num的值可能小于10,并且是0到10之间的任意值(包含0和10)。这是因为多个goroutine并发执行num++操作时,会出现竞态条件(race condition)。由于num++并非原子操作,在并发环境下,不同goroutinenum的读、修改和写操作可能会相互干扰。例如,一个goroutine读取num的值为5,在它还没来得及将num更新为6时,另一个goroutine也读取了num的值5,然后两个goroutine都将num更新为6,这样就丢失了一次num的递增操作。
  2. 为什么?
    • 原因就是Go语言中的普通变量在并发读写时没有自动保护机制。num++操作实际上分为三个步骤:读num的值到寄存器,在寄存器中对值加1,再将寄存器中的值写回num。在多goroutine并发执行时,这些步骤可能会交叉进行,导致结果不可预测。
  3. 如何确保得到预期的结果?
    • 使用sync.Mutex
package main

import (
    "fmt"
    "sync"
)

func main() {
    var num int = 0
    var mu sync.Mutex
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mu.Lock()
            num++
            mu.Unlock()
        }()
    }
    wg.Wait()
    fmt.Println(num)
}
  • 使用sync/atomic
package main

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

func main() {
    var num int64 = 0
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atomic.AddInt64(&num, 1)
        }()
    }
    wg.Wait()
    fmt.Println(num)
}
  1. 这里涉及到闭包在并发环境下的哪些特性?
    • 共享变量:闭包可以访问和修改其外层函数作用域中的变量,如上述代码中闭包函数可以访问和修改num变量。在并发环境下,这种对共享变量的访问如果不加以控制就会出现竞态条件。
    • 延迟绑定:闭包函数在定义时不会立即绑定外层作用域变量的值,而是在运行时绑定。例如,在for循环中创建闭包,闭包中对循环变量i的访问在运行时才确定其值。如果在闭包运行时i的值已经发生了变化,可能会导致不符合预期的结果。在上述代码中虽然没有直接体现i的延迟绑定问题,但在更复杂的场景下,这也是需要注意的特性。