MST

星途 面试题库

面试题:Go语言中复杂业务下Goroutine优雅退出与资源清理的设计与优化

假设你正在开发一个涉及多个Goroutine协同工作的复杂业务系统,这些Goroutine之间存在数据交互和依赖关系。当系统需要关闭时,如何设计一个通用且高效的机制来保证所有Goroutine优雅退出并完成相关资源清理,同时要考虑如何避免死锁、数据一致性等问题,请详细阐述设计思路并给出关键代码示例。
17.7万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 信号处理:使用Go语言内置的os/signal包来捕获系统退出信号(如SIGINTSIGTERM)。这使得程序能够在接收到这些信号时,启动关闭流程。
  2. 上下文(Context):使用context.Context来控制Goroutine的生命周期。context.Context可以携带截止时间、取消信号等信息,传递给各个Goroutine,让它们能够感知到系统关闭的信号并及时退出。
  3. WaitGroup:使用sync.WaitGroup来等待所有Goroutine完成任务。每个Goroutine在启动时向WaitGroup添加计数,在退出时减少计数。主程序使用WaitGroupWait方法等待所有Goroutine完成。
  4. 数据一致性:在Goroutine之间共享数据时,使用sync.Mutexsync.RWMutex等同步原语来保证数据一致性。同时,在关闭流程中,确保所有数据的修改操作已经完成,再进行资源清理。
  5. 避免死锁:在设计Goroutine之间的依赖关系和同步操作时,要特别注意避免死锁。例如,确保锁的获取顺序一致,避免循环依赖等。

关键代码示例

package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "sync"
    "syscall"
    "time"
)

func worker(ctx context.Context, wg *sync.WaitGroup, id int) {
    defer wg.Done()
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Worker %d received shutdown signal, cleaning up...\n", id)
            // 进行资源清理操作
            time.Sleep(1 * time.Second) // 模拟资源清理时间
            fmt.Printf("Worker %d cleanup finished\n", id)
            return
        default:
            fmt.Printf("Worker %d is working...\n", id)
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    var wg sync.WaitGroup
    ctx, cancel := context.WithCancel(context.Background())

    // 启动多个Goroutine
    numWorkers := 3
    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go worker(ctx, &wg, i)
    }

    // 捕获系统退出信号
    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        sig := <-sigs
        fmt.Println()
        fmt.Println(sig)
        cancel() // 发送取消信号给所有Goroutine
    }()

    wg.Wait() // 等待所有Goroutine完成
    fmt.Println("All workers have been gracefully stopped")
}

代码说明

  1. worker函数:每个Goroutine执行worker函数,它使用select语句监听ctx.Done()通道。当接收到取消信号时,进行资源清理并返回。
  2. main函数
    • 使用context.WithCancel创建一个可取消的上下文ctx和取消函数cancel
    • 启动多个Goroutine,并为每个Goroutine添加WaitGroup计数。
    • 使用os/signal包捕获SIGINTSIGTERM信号。当接收到信号时,调用cancel函数取消上下文,通知所有Goroutine退出。
    • 最后使用wg.Wait()等待所有Goroutine完成清理工作。