Go 中 Channel 可能会引发 Goroutine 泄漏

疑问

什么是 Goroutine 泄漏?

Goroutine 泄漏是指 Goroutine 在程序中被创建后,由于某种原因无法正常结束,并且永远不会被垃圾回收(GC)。这会导致 Goroutine 占用的资源(如内存、栈空间等)无法释放,随着时间的推移,可能会耗尽系统资源,导致程序崩溃。

Channel 如何导致 Goroutine 泄漏?

Channel 是 Goroutine 之间同步和通信的重要机制。但是,如果 Channel 的使用不当,就可能导致 Goroutine 阻塞并最终泄漏。以下是导致泄漏的常见场景:

  • 发送阻塞: Goroutine 尝试向一个已满的无缓冲 Channel 或已满的有缓冲 Channel 发送数据,如果没有其他 Goroutine 接收数据,发送操作会阻塞。

  • 接收阻塞: Goroutine 尝试从一个空的无缓冲 Channel 或空的有缓冲 Channel 接收数据,如果没有其他 Goroutine 发送数据,接收操作会阻塞。

泄漏的原因是 goroutine 操作 channel 后,处于发送或接收阻塞状态,而 channel 处于满或空的状态,一直得不到改变。同时,垃圾回收器也不会回收此类资源,进而导致 gouroutine 会一直处于等待队列中,不见天日。

代码示例

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    ch := make(chan int) // 无缓冲 Channel

    go func() {
        ch <- 1 // 第一次发送成功(Channel 未满)
        fmt.Println("第一次发送成功")
        ch <- 2 // 第二次发送永久阻塞(Channel 已满且无接收者)
        fmt.Println("第二次发送成功(永远不会执行)")
    }()

    time.Sleep(500 * time.Millisecond)
    fmt.Println("接收到:", <-ch) // 只消费一次数据

    // 监控 Goroutine 数量
    for {
        fmt.Printf("当前 Goroutine 数量: %d\n", runtime.NumGoroutine()) // 2
        time.Sleep(1 * time.Second)
    }
}

在线运行