MST

星途 面试题库

面试题:Go中defer和recover与并发编程的结合

在Go的并发编程场景下,有多个goroutine同时执行不同的任务,其中某个goroutine可能因为数据处理错误触发panic。请设计一个方案,使用defer和recover机制,在主goroutine中捕获并处理这些子goroutine的panic,同时要保证其他正常的goroutine不受影响继续执行。描述整体的设计思路,并编写关键代码实现。还要分析在这种并发场景下使用defer和recover可能面临的问题及解决方案。
24.4万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 在每个子goroutine中使用defer语句来捕获panic
  2. panic发生时,通过recover函数捕获panic信息。
  3. 将捕获到的panic信息通过通道(channel)传递给主goroutine进行处理。
  4. goroutine从通道接收panic信息并进行相应处理,同时不影响其他正常运行的goroutine

关键代码实现

package main

import (
    "fmt"
)

func worker(id int, resultChan chan int, panicChan chan interface{}) {
    defer func() {
        if r := recover(); r != nil {
            panicChan <- fmt.Sprintf("goroutine %d panicked: %v", id, r)
        }
    }()

    // 模拟可能发生panic的任务
    if id == 3 {
        panic("模拟数据处理错误")
    }
    result := id * 2
    resultChan <- result
}

func main() {
    numGoroutines := 5
    resultChan := make(chan int, numGoroutines)
    panicChan := make(chan interface{}, numGoroutines)

    for i := 1; i <= numGoroutines; i++ {
        go worker(i, resultChan, panicChan)
    }

    go func() {
        for p := range panicChan {
            fmt.Println(p)
        }
    }()

    for i := 1; i <= numGoroutines; i++ {
        select {
        case result := <-resultChan:
            fmt.Printf("goroutine %d 计算结果: %d\n", i, result)
        }
    }

    close(resultChan)
    close(panicChan)
}

可能面临的问题及解决方案

问题

  1. 通道阻塞:如果主goroutine没有及时从panicChan中读取数据,可能导致panicChan阻塞,进而影响子goroutinerecover操作,使得子goroutine无法正常结束。
  2. 资源泄漏:如果goroutinepanic后没有正确清理资源,可能导致资源泄漏。

解决方案

  1. 针对通道阻塞:可以在主goroutine中启动一个单独的goroutine来处理panicChan中的数据,如代码中的go func() { for p := range panicChan { fmt.Println(p) } }()。这样可以确保panicChan不会阻塞。
  2. 针对资源泄漏:在defer函数中除了使用recover捕获panic,还应进行必要的资源清理操作,例如关闭文件、数据库连接等。例如:
func worker(id int, resultChan chan int, panicChan chan interface{}) {
    file, err := os.Open("test.txt")
    if err != nil {
        panic(fmt.Sprintf("打开文件失败: %v", err))
    }
    defer func() {
        file.Close()
        if r := recover(); r != nil {
            panicChan <- fmt.Sprintf("goroutine %d panicked: %v", id, r)
        }
    }()

    // 模拟可能发生panic的任务
    if id == 3 {
        panic("模拟数据处理错误")
    }
    result := id * 2
    resultChan <- result
}

这样可以确保在goroutine发生panic时,文件资源能被正确关闭,避免资源泄漏。