Golang 几种使用 Channel 的错误姿势

文摘   2024-08-31 18:18   中国香港  

Go 的 goroutine 能够让繁琐的并发变得简单易用。Go 不能没有 channel 就像西方不能失去耶路撒冷。Channel 非常神奇,即使是经验丰富的工程师也会被它绊倒。下面让我们来谈谈开发人员在使用 Go 中的 Channel 时常犯的一些错误,以及如何避免这些错误。

Deadlocks

死锁是使用 channel 时可能遇到的最频繁的问题。当一个程序在等待永远不会发生的事情时被卡住,就会出现死锁。想象一下,你试图将数据发送到一个 channel,但另一边却没有人接收数据。你的程序就这样停在那里,什么也不做。来看看这段代码:

func main() {
    ch := make(chan int)
    ch <- 1
    fmt.Println(<-ch)
}

该程序之所以挂起的原因是,代码试图向一个未缓冲通道发送数值,而没有任何 goroutine 可以接收该数值。解决方法很简单:使用 goroutine 发送值。下面是解决方法:

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

通过生成一个 goroutine 来处理发送,就能确保主 goroutine 可以接收数据,程序就不会卡住。

Buffered Channels: 不要滥用缓冲区

当你想发送多个值而又不想立即阻塞时,缓冲通道是个不错的选择,但你需要小心使用。缓冲通道就像是数据的等待室。

举个通俗的例子,假设你正在经营一家小邮局。等候区只有一把椅子。这就像一个容量为 1 的 buffer channel。现在,如果有两个人来邮寄包裹,会发生什么情况呢?

第一个人坐下,没问题。但当第二个人到达时,他们就只能站在外面了,因为没有更多的空间。这正是本代码示例中发生的情况:

func main() {
    ch := make(chan int, 1)
    
    ch <- 1
    ch <- 2  // This will block because the buffer is full
    
    fmt.Println(<-ch)
}

我们的候车室只能容纳一个 "人"(在这里是一个号码),否则就会堵塞。

但如果我们把候车室变得更大呢?

func main() {
    ch := make(chan int, 2)
    
    ch <- 1
    ch <- 2
    
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

现在,我们的候车室里有了两把椅子!两位 "顾客 "都能坐得舒服了,我们的小邮局也能顺利运转了。

所以当我们使用 buffer channel 的时候,要确保缓冲区足够大,否则可能会导致死锁。

Closing Channels: 不要忘记关闭

另一个高频的的错误是在使用完 channel 后忘记关闭。如果不关闭 channel,等待从 channel 接收数据的程序可能会一直等待并且永远不会到来的数据。

func main() {
    ch := make(chan int)
    
    go func() {
        for i := 0; i < 5; i++ {
            ch <- i
        }
    }()
    
    for i := range ch {
        fmt.Println(i)
    }
}

这段代码将打印 0 到 4 的数字,但随后会无限期挂起,因为 range 循环在等待更多数据。channel 从未关闭,因此循环不知道何时停止。

解决方法是什么?发送完数据后关闭通道:

func main() {
    ch := make(chan int)
    
    go func() {
        for i := 0; i < 5; i++ {
            ch <- i
        }
        close(ch)
    }()
    
    for i := range ch {
        fmt.Println(i)
    }
}

现在,当循环收到通道关闭信号时,它就知道要停止了。

以上就是在使用 channel 时候容易出现的高频错误,大家可以牢记在心避免踩坑。

Go Official Blog
Golang官方博客的资讯翻译及独家解读
 最新文章