MST
星途 面试题库

面试题:Go中如何优雅地停止一个Goroutine

在Go语言中,当我们启动了一个Goroutine来执行某个任务,假设这个任务可能是一个长时间运行的循环操作,例如不断从网络读取数据。请描述如何优雅地停止这个Goroutine,避免资源泄漏和数据竞争问题。同时,请给出一个简单的代码示例。
48.5万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 优雅停止Goroutine的方法
    • 使用上下文(context):context包提供了一种取消Goroutine的方法,它可以在多个Goroutine之间传递取消信号,并且在取消时清理相关资源。context主要有context.Backgroundcontext.WithCancelcontext.WithDeadlinecontext.WithTimeout等函数来创建不同类型的上下文。
    • 使用通道(channel):通过向Goroutine发送一个信号(比如一个布尔值)来通知它停止。当Goroutine接收到这个信号时,它会停止执行循环并清理资源。
  2. 代码示例(使用通道)
package main

import (
    "fmt"
    "time"
)

func main() {
    stopCh := make(chan struct{})
    go func() {
        for {
            select {
            case <-stopCh:
                fmt.Println("Goroutine stopped")
                return
            default:
                // 模拟从网络读取数据的长时间运行操作
                fmt.Println("Reading data from network...")
                time.Sleep(1 * time.Second)
            }
        }
    }()

    // 模拟一段时间后停止Goroutine
    time.Sleep(3 * time.Second)
    close(stopCh)
    time.Sleep(1 * time.Second) // 等待Goroutine完全停止
}
  1. 代码示例(使用上下文)
package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("Goroutine stopped")
                return
            default:
                // 模拟从网络读取数据的长时间运行操作
                fmt.Println("Reading data from network...")
                time.Sleep(1 * time.Second)
            }
        }
    }(ctx)

    time.Sleep(5 * time.Second)
}

在上述代码中:

  • 使用通道的示例:通过stopCh通道来通知Goroutine停止。select语句中的default分支模拟从网络读取数据的操作,当stopCh接收到信号时(通过close(stopCh)关闭通道),Goroutine会停止运行。
  • 使用上下文的示例:通过context.WithTimeout创建了一个带有超时的上下文ctxcancel函数用于手动取消上下文(虽然在这个例子中没有手动调用cancel,因为设置了超时)。在Goroutine内部,select语句监听ctx.Done()通道,当接收到取消信号时,Goroutine停止运行。