MST

星途 面试题库

面试题:Go中Goroutine卡住的常见原因及简单解决方案

在Go语言中,Goroutine卡住是一个常见问题。请列举至少两个可能导致Goroutine卡住的原因,并针对每个原因给出一种基本的解决方案。
13.8万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

原因1:死锁

  • 描述:多个Goroutine相互等待对方释放资源,形成循环等待,导致所有相关Goroutine卡住。例如,两个Goroutine分别持有对方需要的锁并尝试获取对方的锁。
  • 解决方案:仔细设计资源获取和释放的顺序,避免循环依赖。可以使用context包来设置操作的截止时间,当操作超时时主动取消,避免死锁一直等待。例如:
package main

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

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

    ch := make(chan int)
    go func() {
        select {
        case <-ctx.Done():
            return
        case ch <- 1:
        }
    }()

    select {
    case <-ctx.Done():
        fmt.Println("操作超时")
    case res := <-ch:
        fmt.Println("获取到结果:", res)
    }
}

原因2:无缓冲通道的收发阻塞

  • 描述:当向无缓冲通道发送数据时,如果没有接收方准备好接收,发送操作会阻塞;同样,从无缓冲通道接收数据时,如果没有发送方准备好发送,接收操作会阻塞。如果这种阻塞一直持续,Goroutine就会卡住。
  • 解决方案:合理使用带缓冲的通道,给通道设置适当的缓冲区大小,使得发送和接收操作不会立即阻塞。或者在发送/接收操作时使用select语句结合default分支来避免阻塞。例如:
package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 1)
    go func() {
        ch <- 1
    }()

    select {
    case res := <-ch:
        fmt.Println("获取到结果:", res)
    default:
        fmt.Println("通道暂时无数据")
    }
}

原因3:无限循环且无调度点

  • 描述:Goroutine内部执行一个没有退出条件的无限循环,并且循环体中没有任何可以让Go调度器有机会切换到其他Goroutine执行的操作(如I/O操作、系统调用、runtime.Gosched()调用等),导致该Goroutine一直占用CPU,其他Goroutine无法执行。
  • 解决方案:在循环体中适当的位置添加runtime.Gosched()调用,主动让出CPU时间片,让调度器有机会调度其他Goroutine。例如:
package main

import (
    "fmt"
    "runtime"
)

func main() {
    go func() {
        for {
            fmt.Println("执行任务")
            runtime.Gosched()
        }
    }()

    // 主Goroutine防止程序退出
    select {}
}