MST

星途 面试题库

面试题:Go语言中通道的缓冲与非缓冲特性

请解释Go语言中缓冲通道和非缓冲通道的区别,在并发编程场景下,它们分别适用于哪些情况?并举例说明如何创建这两种类型的通道。
31.6万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

区别

  1. 缓冲通道
    • 缓冲通道在创建时可以指定一个缓冲区大小。例如make(chan int, 5),这里的5就是缓冲区大小。
    • 向缓冲通道发送数据时,只要缓冲区未满,发送操作就不会阻塞;从缓冲通道接收数据时,只要缓冲区不为空,接收操作就不会阻塞。
  2. 非缓冲通道
    • 非缓冲通道创建时没有缓冲区大小(例如make(chan int))。
    • 向非缓冲通道发送数据时,会阻塞直到有其他 goroutine 从该通道接收数据;从非缓冲通道接收数据时,会阻塞直到有其他 goroutine 向该通道发送数据。可以说非缓冲通道是同步的,发送和接收操作会在同一时刻完成,就像在两个 goroutine 之间传递接力棒。

适用场景

  1. 缓冲通道
    • 解耦生产者 - 消费者模型:当生产者和消费者的速度不一致时,缓冲通道可以作为一个临时的存储区域。例如,在一个日志记录系统中,生产者(产生日志的模块)可能会快速地生成日志消息,而消费者(写入日志文件的模块)可能处理速度相对较慢。使用缓冲通道,生产者可以先将日志消息发送到缓冲通道,只要缓冲区未满就不会阻塞,这样生产者可以继续执行其他任务,而消费者按自己的速度从通道中读取日志并写入文件。
    • 数据批量处理:在需要批量处理数据的场景下,缓冲通道可以收集一定数量的数据后再统一处理。比如在图像处理中,多个 goroutine 可能会将处理后的图像数据发送到一个缓冲通道,当通道中的图像数据达到一定数量(缓冲区满)时,另一个 goroutine 可以一次性读取这些数据并进行打包存储。
  2. 非缓冲通道
    • 同步操作:用于需要精确同步的场景。例如,在分布式系统中,一个主节点需要等待所有子节点完成某项任务后再进行下一步操作。主节点可以通过非缓冲通道向子节点发送任务启动信号,然后等待子节点通过非缓冲通道返回完成信号,确保所有子节点都完成任务后再继续。
    • 数据传递并立即处理:当需要确保数据在发送后立即被处理时,非缓冲通道很有用。比如在一个加密和解密的场景中,一个 goroutine 生成需要加密的数据并通过非缓冲通道发送给负责加密的 goroutine,加密后的结果又通过另一个非缓冲通道发送给负责处理加密数据的 goroutine,确保数据的及时处理和传递。

创建示例

  1. 创建缓冲通道
package main

import "fmt"

func main() {
    // 创建一个缓冲通道,缓冲区大小为 3
    bufferedChan := make(chan int, 3)
    bufferedChan <- 1
    bufferedChan <- 2
    bufferedChan <- 3
    // 这里不会阻塞,因为缓冲区还没满
    // 尝试再发送一个数据,会阻塞,因为缓冲区已满
    // bufferedChan <- 4 
    num := <-bufferedChan
    fmt.Println(num)
}
  1. 创建非缓冲通道
package main

import "fmt"

func main() {
    // 创建一个非缓冲通道
    unbufferedChan := make(chan int)
    go func() {
        unbufferedChan <- 10
    }()
    num := <-unbufferedChan
    fmt.Println(num)
}

在上面创建非缓冲通道的示例中,主 goroutine 如果没有启动一个 goroutine 向通道发送数据就直接接收,会导致死锁,因为没有 goroutine 会向通道发送数据。所以这里启动了一个匿名 goroutine 来发送数据,确保接收操作不会阻塞。