MST

星途 面试题库

面试题:Go中Goroutine卡住可能由哪些常见原因导致?

请简要阐述在Go语言编程中,可能使Goroutine出现卡住情况的常见原因,比如从同步原语、channel使用等方面分析。
45.4万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

同步原语相关

  1. 互斥锁死锁
    • 当多个Goroutine相互等待对方释放已经持有的互斥锁时,就会发生死锁。例如,Goroutine A获取了互斥锁mu1,然后试图获取mu2,而Goroutine B获取了mu2,同时又试图获取mu1,这种情况下两个Goroutine都会卡住等待对方释放锁,从而导致死锁。
    var mu1, mu2 sync.Mutex
    go func() {
        mu1.Lock()
        defer mu1.Unlock()
        mu2.Lock()
        defer mu2.Unlock()
    }()
    go func() {
        mu2.Lock()
        defer mu2.Unlock()
        mu1.Lock()
        defer mu1.Unlock()
    }()
    
  2. 条件变量使用不当
    • 如果在使用sync.Cond时,没有正确的在条件变量等待前获取相关的互斥锁,或者在等待后没有重新获取锁,可能导致Goroutine卡住。另外,如果没有其他Goroutine来唤醒等待在条件变量上的Goroutine,也会造成该Goroutine一直处于等待状态。
    var mu sync.Mutex
    cond := sync.NewCond(&mu)
    go func() {
        // 未获取锁就等待,错误做法
        cond.Wait()
    }()
    

Channel使用相关

  1. 无缓冲Channel收发不匹配
    • 对于无缓冲Channel,发送操作会阻塞直到有其他Goroutine接收数据,接收操作也会阻塞直到有其他Goroutine发送数据。如果只有一个Goroutine进行发送或者接收操作,而没有对应的接收或发送方,就会导致该Goroutine卡住。
    ch := make(chan int)
    go func() {
        ch <- 1 // 这里会一直阻塞,因为没有接收者
    }()
    
  2. 缓冲Channel满或空时的阻塞
    • 对于有缓冲Channel,当缓冲已满时,发送操作会阻塞;当缓冲为空时,接收操作会阻塞。如果在程序逻辑中没有合理处理这些情况,可能会导致Goroutine卡住。例如,不断向已满的缓冲Channel发送数据而没有及时接收。
    ch := make(chan int, 2)
    ch <- 1
    ch <- 2
    go func() {
        ch <- 3 // 这里会阻塞,因为缓冲已满
    }()
    
  3. 关闭Channel后继续发送
    • 关闭Channel后,再向其发送数据会导致运行时恐慌(panic),如果程序中没有恰当处理这种情况,可能导致整个程序异常,间接使得其他相关Goroutine卡住。而在接收端,如果没有通过ok标志检查Channel是否关闭就继续接收,也可能出现意外情况。
    ch := make(chan int)
    close(ch)
    go func() {
        ch <- 1 // 这里会panic
    }()
    
  4. 未关闭的Channel导致的泄漏
    • 如果一个Goroutine向一个Channel发送数据,而该Channel没有被接收端正确关闭,并且发送Goroutine一直在等待接收端接收数据,这就会导致该发送Goroutine一直阻塞,造成资源泄漏。同时,在接收端,如果没有处理好Channel的关闭情况,也可能会导致Goroutine卡在接收操作上。
    ch := make(chan int)
    go func() {
        for {
            data, ok := <-ch
            if!ok {
                break
            }
            // 处理data
        }
    }()
    // 没有关闭ch,发送Goroutine可能一直阻塞
    

其他情况

  1. 系统调用阻塞
    • 如果Goroutine执行一些系统调用(如syscall包中的函数),这些系统调用本身可能会阻塞,例如等待I/O操作完成(如文件读取、网络请求等)。如果没有正确处理这些阻塞操作,可能会导致Goroutine长时间卡住。例如,使用syscall.Read读取一个网络套接字,但网络连接出现问题,数据无法及时到达,Goroutine就会一直阻塞在该读取操作上。
  2. 无限循环无调度点
    • 如果Goroutine内部有一个无限循环且没有任何可以让Go运行时调度器进行调度的操作(如Channel操作、系统调用、调用sync.Mutex的方法等),那么该Goroutine会一直占用CPU资源,不会让出执行权,导致其他Goroutine无法执行,看起来就像是卡住了。
    go func() {
        for {
            // 这里没有调度点,其他Goroutine无法执行
        }
    }()