MST

星途 面试题库

面试题:Go语言defer在并发编程中资源清理的挑战与解决方案

在Go语言的并发编程场景下,使用defer进行资源清理会面临哪些特殊的挑战?例如,多个goroutine同时访问和清理同一个资源。请详细阐述这些挑战,并提出至少两种有效的解决方案,同时给出相应的代码示例及解释。
24.6万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

面临的挑战

  1. 资源竞争:多个goroutine同时通过defer清理同一资源,可能导致资源竞争问题,例如文件描述符的重复关闭,数据库连接的重复释放等,这会引发程序崩溃或数据不一致。
  2. 死锁风险:如果在defer清理函数中存在同步操作(如获取锁),并且不同goroutine的defer清理顺序不当,可能会导致死锁。例如,goroutine A在defer中获取锁L1,然后等待锁L2,而goroutine B在defer中获取锁L2,然后等待锁L1,就会形成死锁。

解决方案

  1. 使用互斥锁(Mutex)
    • 原理:通过互斥锁保证同一时间只有一个goroutine可以访问和清理资源,避免资源竞争。
    • 代码示例
package main

import (
    "fmt"
    "sync"
)

var (
    resource = 0
    mu       sync.Mutex
)

func worker(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    defer mu.Unlock()
    // 使用资源
    resource++
    fmt.Println("Using resource, current value:", resource)
    // 模拟业务逻辑
    // 清理资源
    fmt.Println("Cleaning up resource")
    resource--
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go worker(&wg)
    }
    wg.Wait()
    fmt.Println("Final resource value:", resource)
}
- **解释**:在`worker`函数中,通过`mu.Lock()`和`mu.Unlock()`(使用defer确保解锁)保证了同一时间只有一个goroutine可以访问和修改`resource`,避免了资源竞争。

2. 使用sync.Once - 原理sync.Once类型的变量可以保证其函数只被执行一次,无论有多少个goroutine试图调用。这可以用于确保资源只被清理一次。 - 代码示例

package main

import (
    "fmt"
    "sync"
)

var (
    resource = 0
    once     sync.Once
)

func worker(wg *sync.WaitGroup) {
    defer wg.Done()
    // 使用资源
    resource++
    fmt.Println("Using resource, current value:", resource)
    // 模拟业务逻辑
    // 清理资源
    once.Do(func() {
        fmt.Println("Cleaning up resource")
        resource--
    })
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go worker(&wg)
    }
    wg.Wait()
    fmt.Println("Final resource value:", resource)
}
- **解释**:在`worker`函数中,`once.Do`确保了资源清理函数只被执行一次,即使多个goroutine都调用了`once.Do`,这样就避免了重复清理资源的问题。