文章目录
- [🚀 17 - Go 通道 Channel 底层原理 + 实战详解](#🚀 17 - Go 通道 Channel 底层原理 + 实战详解)
- [channel 是什么?](#channel 是什么?)
-
- [💡 核心思想](#💡 核心思想)
- [channel 类型](#channel 类型)
-
- [无缓冲 channel(同步)](#无缓冲 channel(同步))
- [有缓冲 channel(异步)](#有缓冲 channel(异步))
- [channel 底层数据结构(重点🔥)](#channel 底层数据结构(重点🔥))
- [💡 核心结构解析](#💡 核心结构解析)
-
- 环形队列(buffer)
- [sendq / recvq(阻塞队列)](#sendq / recvq(阻塞队列))
- [mutex 锁](#mutex 锁)
- [channel 发送 / 接收流程(核心🔥)](#channel 发送 / 接收流程(核心🔥))
-
- [📤 发送(ch <- value)](#📤 发送(ch <- value))
-
- 有等待接收者
- [buffer 未满](#buffer 未满)
- [buffer 满](#buffer 满)
- [📥 接收(<-ch)](#📥 接收(<-ch))
-
- [buffer 有数据](#buffer 有数据)
- [buffer 为空](#buffer 为空)
- [channel 实战示例](#channel 实战示例)
-
- [🧪 goroutine 通信](#🧪 goroutine 通信)
- [🧪 worker pool(经典🔥)](#🧪 worker pool(经典🔥))
- [🧪 pipeline(流水线模式)](#🧪 pipeline(流水线模式))
- [channel 常见问题(面试必考🔥)](#channel 常见问题(面试必考🔥))
-
- [❌ 死锁](#❌ 死锁)
- [❌close panic](#❌close panic)
- [❌ 重复 close](#❌ 重复 close)
- [channel vs mutex(核心对比)](#channel vs mutex(核心对比))
- [channel 使用原则(非常重要)](#channel 使用原则(非常重要))
- 一句话总结
🚀 17 - Go 通道 Channel 底层原理 + 实战详解
channel 是 Go 并发模型的核心,它不仅是"通信工具",更是 goroutine 之间的"同步原语"。
channel 是什么?
在 Go 中:
go
ch := make(chan int)
👉 可以拆开理解:
make:Go 内置函数,用来创建引用类型chan int:表示一个 传递 int 类型数据的 channelch:变量名,代表这个 channel
📌 一句话解释:
创建一个"只能传递 int 类型数据的 channel"
👉 channel 本质是:
goroutine 之间传递数据 + 同步执行的管道
💡 核心思想
Go 并发哲学:
❗ 不要通过共享内存通信,而要通过通信共享内存
channel 类型
无缓冲 channel(同步)
go
ch := make(chan int)
特点:
- 发送和接收必须同时发生
- 否则阻塞
- 用于"强同步"
有缓冲 channel(异步)
go
ch := make(chan int, 3)
特点:
- 可以暂存 3 个 int 类型数据的缓冲区
- buffer 未满 → 不阻塞
- buffer 满 → 阻塞
- 类似队列
channel 底层数据结构(重点🔥)
Go runtime 中 channel 对应结构:
go
type hchan struct {
qcount uint // 当前队列元素数量
dataqsiz uint // 缓冲区大小
buf unsafe.Pointer // 环形队列
elemsize uint16
sendx uint // 发送索引
recvx uint // 接收索引
recvq waitq // 接收等待队列
sendq waitq // 发送等待队列
lock mutex // 互斥锁
}
💡 核心结构解析
环形队列(buffer)
text
buf -> [ 1 | 2 | 3 | _ | _ ]
👉 用于存储数据
sendq / recvq(阻塞队列)
| 队列 | 作用 |
|---|---|
| sendq | 发送被阻塞的 goroutine |
| recvq | 接收被阻塞的 goroutine |
mutex 锁
👉 保证 channel 并发安全
channel 发送 / 接收流程(核心🔥)
📤 发送(ch <- value)
有等待接收者
👉 直接拷贝数据给接收者
buffer 未满
👉 放入环形队列
buffer 满
👉 当前 goroutine 进入 sendq 阻塞
📥 接收(<-ch)
buffer 有数据
👉 直接读取
buffer 为空
👉 当前 goroutine 进入 recvq 阻塞
channel 实战示例
🧪 goroutine 通信
go
package main
import "fmt"
// worker 函数:负责向 channel 发送数据
func worker(ch chan int) {
ch <- 100
// 向 channel 发送 100
// 如果没有接收者,这里会阻塞
}
func main() {
ch := make(chan int)
// 创建一个无缓冲 channel(同步 channel)
go worker(ch)
// 启动 goroutine 执行 worker
// worker 会尝试发送数据到 channel
v := <-ch
// 从 channel 接收数据
// 如果没有数据,这里会阻塞等待
fmt.Println(v)
// 输出:100
}
无缓冲 channel 的本质是 goroutine 之间的"同步交接点",发送和接收必须同时发生,否则双方都会阻塞。
👉 特点:
- 同步通信
- 自动阻塞配对
🧪 worker pool(经典🔥)
go
package main
import "fmt"
// worker:工作协程
// jobs:只读 channel(任务队列)
// results:只写 channel(结果队列)
func worker(jobs <-chan int, results chan<- int) {
for j := range jobs {
// 不断从 jobs 中读取任务
// 如果 jobs 关闭,循环自动退出
results <- j * 2
// 处理任务并将结果写入 results
}
}
func main() {
jobs := make(chan int, 5)
// 任务队列(带缓冲)
results := make(chan int, 5)
// 结果队列(带缓冲)
// 启动 3 个 worker 协程
for i := 0; i < 3; i++ {
go worker(jobs, results)
}
// 投递 5 个任务
for i := 0; i < 5; i++ {
jobs <- i
}
close(jobs)
// 关键点:关闭 jobs channel
// 告诉 worker:没有新任务了
// 收集结果
for i := 0; i < 5; i++ {
fmt.Println(<-results)
}
}
worker pool 是 Go 并发中最经典的模式,通过 channel 进行任务分发,通过 goroutine 并行执行,从而实现高效并发处理。
🧪 pipeline(流水线模式)
go
package main
import "fmt"
// gen:生成器(数据源)
// 把 nums 转换为 channel 流
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
// 把数据逐个写入 channel
}
close(out)
// 数据发送完毕后关闭 channel
}()
return out
}
// sq:处理器(计算阶段)
// 对输入流进行平方运算
func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
// 从上游 channel 不断读取数据
out <- n * n
// 处理后写入下游 channel
}
close(out)
// 处理完成后关闭 channel
}()
return out
}
func main() {
// 第一阶段:生成数据流
// 第二阶段:处理数据流(平方)
out := sq(gen(2, 3))
// 消费最终结果
for n := range out {
fmt.Println(n) // 输出:4,9
}
}
Pipeline 模式通过 goroutine + channel 将数据处理拆分为多个阶段,实现了流式、高并发、低耦合的数据处理模型。
👉 特点:
- 数据流式处理
- 解耦业务逻辑
channel 常见问题(面试必考🔥)
❌ 死锁
go
ch := make(chan int)
ch <- 1
👉 原因:
- 无接收者
- main 阻塞
❌close panic
go
ch <- 1
close(ch)
ch <- 2 // panic
❌ 重复 close
go
close(ch)
close(ch) // panic
channel vs mutex(核心对比)
| 对比 | channel | mutex |
|---|---|---|
| 思想 | 通信 | 共享内存 |
| 安全性 | 高 | 中 |
| 性能 | 稍慢 | 更快 |
| 使用场景 | 并发协作 | 资源保护 |
channel 使用原则(非常重要)
✔ 推荐:
- goroutine 通信
- worker pool
- pipeline
❌ 不推荐:
- 当锁用
- 复杂状态共享
- 频繁小对象传递
一句话总结
channel 的本质是一个"带锁的环形队列 + goroutine 阻塞调度器",它不仅是通信工具,更是 Go 并发模型的核心调度机制。