面试题答案
一键面试区别
- 缓冲通道:
- 缓冲通道在创建时可以指定一个缓冲区大小。例如
make(chan int, 5)
,这里的5
就是缓冲区大小。 - 向缓冲通道发送数据时,只要缓冲区未满,发送操作就不会阻塞;从缓冲通道接收数据时,只要缓冲区不为空,接收操作就不会阻塞。
- 缓冲通道在创建时可以指定一个缓冲区大小。例如
- 非缓冲通道:
- 非缓冲通道创建时没有缓冲区大小(例如
make(chan int)
)。 - 向非缓冲通道发送数据时,会阻塞直到有其他 goroutine 从该通道接收数据;从非缓冲通道接收数据时,会阻塞直到有其他 goroutine 向该通道发送数据。可以说非缓冲通道是同步的,发送和接收操作会在同一时刻完成,就像在两个 goroutine 之间传递接力棒。
- 非缓冲通道创建时没有缓冲区大小(例如
适用场景
- 缓冲通道:
- 解耦生产者 - 消费者模型:当生产者和消费者的速度不一致时,缓冲通道可以作为一个临时的存储区域。例如,在一个日志记录系统中,生产者(产生日志的模块)可能会快速地生成日志消息,而消费者(写入日志文件的模块)可能处理速度相对较慢。使用缓冲通道,生产者可以先将日志消息发送到缓冲通道,只要缓冲区未满就不会阻塞,这样生产者可以继续执行其他任务,而消费者按自己的速度从通道中读取日志并写入文件。
- 数据批量处理:在需要批量处理数据的场景下,缓冲通道可以收集一定数量的数据后再统一处理。比如在图像处理中,多个 goroutine 可能会将处理后的图像数据发送到一个缓冲通道,当通道中的图像数据达到一定数量(缓冲区满)时,另一个 goroutine 可以一次性读取这些数据并进行打包存储。
- 非缓冲通道:
- 同步操作:用于需要精确同步的场景。例如,在分布式系统中,一个主节点需要等待所有子节点完成某项任务后再进行下一步操作。主节点可以通过非缓冲通道向子节点发送任务启动信号,然后等待子节点通过非缓冲通道返回完成信号,确保所有子节点都完成任务后再继续。
- 数据传递并立即处理:当需要确保数据在发送后立即被处理时,非缓冲通道很有用。比如在一个加密和解密的场景中,一个 goroutine 生成需要加密的数据并通过非缓冲通道发送给负责加密的 goroutine,加密后的结果又通过另一个非缓冲通道发送给负责处理加密数据的 goroutine,确保数据的及时处理和传递。
创建示例
- 创建缓冲通道:
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)
}
- 创建非缓冲通道:
package main
import "fmt"
func main() {
// 创建一个非缓冲通道
unbufferedChan := make(chan int)
go func() {
unbufferedChan <- 10
}()
num := <-unbufferedChan
fmt.Println(num)
}
在上面创建非缓冲通道的示例中,主 goroutine 如果没有启动一个 goroutine 向通道发送数据就直接接收,会导致死锁,因为没有 goroutine 会向通道发送数据。所以这里启动了一个匿名 goroutine 来发送数据,确保接收操作不会阻塞。