MST

星途 面试题库

面试题:Go中channel关闭在复杂并发场景下的最佳实践

在一个复杂的分布式系统模拟场景中,有多个微服务(以goroutine模拟)通过channel进行通信。其中部分微服务会根据外部信号(如系统配置更新)停止向channel发送数据,然后整个系统需要在所有相关微服务完成当前数据发送任务后安全关闭相关channel。请详细设计一个方案来实现这一过程,包括如何避免资源泄漏、保证数据一致性,并对方案中的关键代码段进行解释。
41.7万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 设计思路

    • 使用一个全局的context.Context来管理整个系统的生命周期。当接收到外部信号(如系统配置更新)时,取消这个context
    • 每个微服务(goroutine)使用这个context来判断是否应该停止发送数据。
    • 使用sync.WaitGroup来等待所有微服务完成当前数据发送任务。
    • 为每个channel设置一个“结束信号”机制,确保在所有数据发送完成后安全关闭channel
  2. 关键代码段及解释

    package main
    
    import (
        "context"
        "fmt"
        "sync"
        "time"
    )
    
    func main() {
        ctx, cancel := context.WithCancel(context.Background())
        var wg sync.WaitGroup
        dataCh := make(chan int)
    
        // 模拟微服务1
        wg.Add(1)
        go func(ctx context.Context, wg *sync.WaitGroup) {
            defer wg.Done()
            for {
                select {
                case <-ctx.Done():
                    return
                case dataCh <- 1:
                    fmt.Println("Microservice 1 sent data")
                    time.Sleep(time.Millisecond * 100)
                }
            }
        }(ctx, &wg)
    
        // 模拟微服务2
        wg.Add(1)
        go func(ctx context.Context, wg *sync.WaitGroup) {
            defer wg.Done()
            for {
                select {
                case <-ctx.Done():
                    return
                case dataCh <- 2:
                    fmt.Println("Microservice 2 sent data")
                    time.Sleep(time.Millisecond * 100)
                }
            }
        }(ctx, &wg)
    
        // 模拟接收数据
        go func() {
            for data := range dataCh {
                fmt.Println("Received data:", data)
            }
            fmt.Println("Data channel closed")
        }()
    
        // 模拟外部信号,500毫秒后取消context
        time.Sleep(time.Millisecond * 500)
        cancel()
    
        // 等待所有微服务完成任务
        wg.Wait()
        close(dataCh)
        fmt.Println("System shut down")
    }
    
    • context.Context相关代码
      ctx, cancel := context.WithCancel(context.Background())
      
      这里创建了一个可取消的contextctx用于传递取消信号给各个微服务,cancel函数用于触发取消操作。
    • 微服务goroutine中的select语句
      select {
      case <-ctx.Done():
          return
      case dataCh <- 1:
          fmt.Println("Microservice 1 sent data")
          time.Sleep(time.Millisecond * 100)
      }
      
      select语句用于监听ctx.Done()信号,当接收到取消信号时,goroutine会返回,停止向channel发送数据。同时,它还继续向channel发送数据,直到收到取消信号。
    • sync.WaitGroup相关代码
      var wg sync.WaitGroup
      wg.Add(1)
      go func(ctx context.Context, wg *sync.WaitGroup) {
          defer wg.Done()
          //...
      }(ctx, &wg)
      
      wg.Add(1)用于增加等待组的计数,每个微服务goroutine启动时都增加计数。defer wg.Done()goroutine结束时减少计数。wg.Wait()用于阻塞主线程,直到所有微服务goroutine完成任务。
    • 关闭channel代码
      wg.Wait()
      close(dataCh)
      
      在所有微服务完成任务后,关闭channel,避免资源泄漏,并保证数据一致性,因为所有数据都已经发送完成。
  3. 避免资源泄漏和保证数据一致性

    • 避免资源泄漏:通过context来控制goroutine的生命周期,确保在接收到取消信号后,goroutine能正确退出,不会造成goroutine泄漏。在所有goroutine完成任务后关闭channel,避免channel一直处于打开状态造成资源浪费。
    • 保证数据一致性:每个微服务在接收到取消信号前,会继续完成当前的数据发送任务。sync.WaitGroup确保所有微服务都完成任务后才关闭channel,这样保证了所有数据都能被正确发送和接收,从而保证数据一致性。