基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Go 有缓冲 Channel 和无缓冲 Channel 的区别

知识点图片

在 Go 语言中,Channel(通道)是 Goroutine 之间通信和同步的核心机制。根据创建时是否指定容量,Channel 分为 无缓冲(Unbuffered)有缓冲(Buffered) 两种。

它们的核心区别在于 通信机制(同步 vs 异步)阻塞行为


1. 无缓冲 Channel (Unbuffered Channel)

定义: 创建时没有指定容量,或者容量为 0。
语法: ch := make(chan int)ch := make(chan int, 0)

核心特性:同步通信 (Synchronous)

无缓冲 Channel 类似于 “一手交钱,一手交货”

  • 发送阻塞: 发送操作 ch <- x 会一直阻塞,直到有另一个 Goroutine 执行接收操作 <-ch
  • 接收阻塞: 接收操作 <-ch 会一直阻塞,直到有另一个 Goroutine 执行发送操作 ch <- x
  • 强同步保证: 当发送操作完成时,意味着接收者 一定 已经收到了数据。它不仅传递数据,还充当了同步屏障(Synchronization Barrier)。

场景举例

就像打电话,你(发送者)说话时,对方(接收者)必须在线听着,否则你无法把话说完。

代码示例

go
package main

import (
    "fmt"
    "time"
)

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

    go func() {
        fmt.Println("接收者准备就绪...")
        val := <-ch // 接收阻塞,直到主线程发送
        fmt.Println("接收到:", val)
    }()

    time.Sleep(time.Second) // 模拟耗时
    fmt.Println("开始发送...")
    ch <- 1 // 发送阻塞,直到上面的 goroutine 开始接收
    fmt.Println("发送完成")
}

2. 有缓冲 Channel (Buffered Channel)

定义: 创建时指定了大于 0 的容量。
语法: ch := make(chan int, 5) (容量为 5)

核心特性:异步通信 (Asynchronous)

有缓冲 Channel 类似于 “快递柜”“消息队列”

  • 发送行为:
    • 只要缓冲区 未满,发送操作 ch <- x 不会阻塞(发送即走)。
    • 只有当缓冲区 时,发送操作才会阻塞,直到有接收者取走数据腾出空间。
  • 接收行为:
    • 只要缓冲区 不为空,接收操作 <-ch 不会阻塞
    • 只有当缓冲区 时,接收操作才会阻塞,直到有发送者放入数据。
  • 解耦: 发送者和接收者不需要同时在线,允许生产和消费的速度在一定程度上不匹配。

场景举例

就像发短信或发邮件。你(发送者)发完就去忙别的了,对方(接收者)可以在空闲时再看。只有当对方邮箱爆满(缓冲区满)时,你才发不出去。

代码示例

go
package main

import "fmt"

func main() {
    ch := make(chan int, 2) // 容量为 2 的缓冲 Channel

    // 此时没有接收者,但发送不会阻塞,因为缓冲区没满
    ch <- 1
    fmt.Println("发送 1 成功")
    ch <- 2
    fmt.Println("发送 2 成功")

    // ch <- 3 // 如果取消注释,这里会死锁(阻塞),因为缓冲区满了且没人接收

    fmt.Println("接收:", <-ch)
    fmt.Println("接收:", <-ch)
}

3. 核心区别总结表

特性 无缓冲 Channel 有缓冲 Channel
容量 (Capacity) 0 > 0
同步性 强同步 (Synchronous) 异步 (Asynchronous)
发送阻塞条件 没有接收者在等待时 缓冲区 已满
接收阻塞条件 没有发送者在等待时 缓冲区 为空
数据传递保证 发送完成即代表对方已接收 发送完成仅代表数据进入了缓冲区(不保证对方已处理)
类比 握手、打电话 邮箱、传送带

4. 什么时候用哪个?

使用无缓冲 Channel 的场景:

  1. 强制同步: 当你需要保证“A 任务做完,B 任务才能继续”,或者需要确认数据已经被对方处理完毕。
  2. 信号传递 (Signal): 例如 done channel,用于通知主线程子协程已退出。
  3. 避免数据积压: 如果不希望内存中堆积过多的待处理任务。

使用有缓冲 Channel 的场景:

  1. 解耦生产者和消费者: 生产者速度快,消费者速度慢(或反之),缓冲区可以作为“减震器”,平滑处理峰值流量。
  2. 限制并发数 (Semaphore): 利用缓冲区的容量来限制同时运行的 Goroutine 数量(令牌桶模式)。
  3. 性能优化: 减少因等待接收者而导致的 Goroutine 上下文切换开销。

总结

  • 无缓冲 = 保证交付(必须有人接)。
  • 有缓冲 = 保证投递(只要箱子没满就能放进去)。
00:00
00:00