MST

星途 面试题库

面试题:Go语言defer语句在并发场景下的陷阱

在一个并发程序中,多个goroutine都使用了defer语句来释放资源(如数据库连接)。然而,出现了资源竞争问题,导致程序运行异常。请分析可能的原因,并阐述如何避免这种在并发场景下由defer语句引发的陷阱,要求结合具体代码示例说明。
14.9万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

可能原因

  1. 共享资源未同步访问:多个 goroutine 可能同时操作共享的资源(如数据库连接池),而 defer 语句执行时如果没有适当的同步机制,就会导致资源竞争。例如,一个 goroutine 可能在另一个 goroutine 还在使用连接时就尝试释放连接到共享连接池,导致数据不一致或连接状态混乱。
  2. 竞争条件与延迟执行defer 语句延迟到函数返回时执行,在函数执行过程中,多个 goroutine 对共享资源的操作顺序不确定。如果没有正确的同步,可能会出现竞争条件。比如,两个 goroutine 都获取了同一个共享资源,并且都通过 defer 语句计划在函数结束时释放它,但是由于执行顺序不确定,可能导致错误的释放操作。

避免方法

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

import (
    "fmt"
    "sync"
)

var (
    mu sync.Mutex
    resource = 0
)

func useResource() {
    mu.Lock()
    defer mu.Unlock()
    // 使用共享资源
    resource++
    fmt.Println("Using resource, current value:", resource)
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            useResource()
        }()
    }
    wg.Wait()
}

在这个例子中,mu 是一个互斥锁,useResource 函数在使用共享资源 resource 之前先锁定互斥锁,函数结束时通过 defer 语句解锁,这样就避免了多个 goroutine 同时访问 resource 导致的资源竞争。 2. 使用通道(channel:通过通道来传递共享资源,从而避免直接的共享访问。通道可以控制资源的流向和使用顺序。

package main

import (
    "fmt"
    "sync"
)

type Resource struct {
    value int
}

func main() {
    resourceChan := make(chan *Resource, 1)
    resource := &Resource{}
    resourceChan <- resource

    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            res := <-resourceChan
            // 使用资源
            res.value++
            fmt.Println("Using resource, current value:", res.value)
            resourceChan <- res
        }()
    }
    wg.Wait()
    close(resourceChan)
}

在这个例子中,通过通道 resourceChan 来传递 Resource 实例,每个 goroutine 从通道获取资源,使用后再放回通道,保证了同一时间只有一个 goroutine 在使用资源,避免了资源竞争。同时,通道也可以处理资源的生命周期管理,比如当所有 goroutine 完成后关闭通道。