Go Channel `make()` 深入全面讲解

你会彻底掌握:

  • make(chan T)make(chan T, 0)make(chan T, n) 到底有什么区别
  • 通道创建后底层长什么样
  • 容量 = 0 / 容量 > 0 行为差异
  • nil 通道 vs 已 make 通道
  • 如何根据业务选择正确的通道容量
  • 面试 100% 问的核心知识点

一、通道为什么必须用 make

Channel 是 引用类型,和 slice、map 一样。

  • 只声明 var ch chan int,得到的是 nil 通道
  • nil 通道不能用,发送/接收会永久阻塞
  • 必须用 make 分配内存、初始化底层结构体,通道才能工作
go 复制代码
var ch chan int       // nil 通道,不能用
ch = make(chan int)   // 初始化后,才能用

二、make 创建通道的 3 种标准写法

go 复制代码
// 1. 无缓冲通道(容量 0)
ch := make(chan T)

// 2. 无缓冲通道(等价写法)
ch := make(chan T, 0)

// 3. 有缓冲通道(容量 N)
ch := make(chan T, N)

核心参数解释

  • T:通道传输的数据类型(int/string/struct/interface 等)
  • 容量
    • 0 = 无缓冲(同步)
    • N > 0 = 有缓冲(异步)

三、make(chan T) 无缓冲通道(容量=0)

行为规则

  • 必须发送和接收同时就绪
  • 发送方会阻塞,直到有人接收
  • 接收方会阻塞,直到有人发送
  • 强同步

示例

go 复制代码
ch := make(chan int)   // 无缓冲

go func() {
    ch <- 100   // 阻塞,直到 main 取走
}()

fmt.Println(<-ch)  // 取走,发送方继续

底层特点

  • 不创建缓冲区数组
  • 数据直接从发送goroutine拷贝到接收goroutine
  • 纯粹用于同步、信号通知

四、make(chan T, N) 有缓冲通道(N>0)

行为规则

  • 缓冲区未满:发送不阻塞
  • 缓冲区为空:接收不阻塞
  • 缓冲区满:发送阻塞
  • 缓冲区空:接收阻塞
  • 异步通信

示例

go 复制代码
ch := make(chan int, 2)   // 容量2

ch <- 1   // 不阻塞
ch <- 2   // 不阻塞
ch <- 3   // 阻塞(缓冲区满)

底层特点

  • 创建一个长度为 N 的环形数组作为缓冲区
  • 发送先写缓冲区,接收先读缓冲区
  • 解耦发送和接收,削峰、限流、队列

五、make 创建通道的底层原理(极简版)

执行 make(chan T, size) 时,Go 运行时会:

  1. 分配 hchan 结构体内存
  2. 根据容量决定是否创建环形缓冲区数组
  3. 初始化锁、等待队列、下标指针
  4. 返回通道引用(指针)

核心底层结构

go 复制代码
type hchan struct {
    qcount   uint           // 当前元素数量
    dataqsiz uint           // 缓冲区容量(make 传入的 N)
    buf      unsafe.Pointer // 环形缓冲区
    elemsize uint16         // 元素大小
    closed   uint32         // 是否关闭
    sendx    uint           // 发送下标
    recvx    uint           // 接收下标
    recvq    waitq          // 等待的接收者
    sendq    waitq          // 等待的发送者
    lock     mutex          // 并发安全锁
}

一句话总结
make 就是给通道分配内存 + 初始化缓冲区 + 准备运行环境


六、nil 通道 vs make 初始化通道(超级重要)

操作 nil 通道 var ch chan int 已 make 通道 ch := make(chan int)
发送 永久阻塞 正常/阻塞
接收 永久阻塞 正常/阻塞
close panic 正常关闭
len(ch) 0 缓冲区元素个数
cap(ch) 0 缓冲区容量
select case 永远不就绪 正常就绪

结论

不 make 的通道完全不可用,只会造成 goroutine 泄漏、死锁。


七、len() 和 cap() 对通道的意义

go 复制代码
ch := make(chan int, 3)
ch <- 1
ch <- 2

fmt.Println(len(ch)) // 2(缓冲区里有多少数据)
fmt.Println(cap(ch)) // 3(缓冲区总容量)
  • len(ch) :通道缓冲区中待读取的元素数量
  • cap(ch) :创建时 make 指定的总容量

无缓冲通道:

go 复制代码
len(ch) = 0
cap(ch) = 0

八、make 创建单向通道

你不能直接 make 一个只能在当前上下文使用的单向通道,但可以约束

go 复制代码
// 只写
var sendCh chan<- int = make(chan int)

// 只读
var recvCh <-chan int = make(chan int)

作用:用于函数参数,限制权限,提高代码安全。

go 复制代码
func send(ch chan<- int) {  // 只写
    ch <- 100
}

func recv(ch <-chan int) {  // 只读
    <-ch
}

九、通道容量到底该怎么选?(实战指南)

1. 容量 = 0(无缓冲)

适用:

  • 同步等待
  • 信号通知
  • 必须严格等待对方响应

2. 容量 = 1

适用:

  • 互斥锁(类似 mutex)
  • 简单串行控制

3. 容量 > 1(有缓冲)

适用:

  • 流量控制
  • 任务队列
  • 生产者消费者解耦
  • 削峰填谷

4. 容量如何确定?

  • 能小不要大
  • 不要盲目设很大(如 10000),会掩盖并发问题
  • 限流场景:容量 = 最大并发数

十、make 通道常见坑(90% 的人中招)

坑1:只声明不 make

go 复制代码
var ch chan int
ch <- 1   // 永久阻塞 → goroutine 泄漏

坑2:无缓冲通道在同一个 goroutine 收发

go 复制代码
ch := make(chan int)
ch <- 1   // 阻塞,没人接收 → 死锁

坑3:把容量当成"最多发送次数"

容量是缓冲区大小 ,不是发送上限。

满了就阻塞,不是报错。

坑4:make(chan T, -1)

编译错误,容量不能为负数。

坑5:以为 len(ch) == 0 就是通道关闭

错误!

关闭判断必须用:

go 复制代码
v, ok := <-ch
if !ok {
    // 已关闭
}

十一、面试必问(你看完就能秒答)

Q1:make(chan int) 和 make(chan int, 0) 一样吗?

完全一样,都是无缓冲通道。

Q2:通道容量为 0 和 1 有什么区别?

  • 0:同步,必须收发同时就绪
  • 1:异步,可以缓存1个值,发送1次不阻塞

Q3:nil 通道可以用吗?

不可以,收发都会永久阻塞。

Q4:无缓冲通道底层有缓冲区吗?

没有,数据直接 goroutine 到 goroutine。

Q5:cap(ch) 返回什么?

make 时指定的容量。

Q6:为什么通道必须 make?

通道是引用类型,需要分配底层 hchan 结构体和缓冲区。


十二、终极总结(一句话记住)

make(chan T) 创建无缓冲同步通道;
make(chan T, N) 创建有缓冲异步通道;
不 make 就是 nil 通道,永远阻塞;
容量决定是否异步、缓冲区大小。

相关推荐
XMYX-04 小时前
06 - Go 的切片、字典与遍历:从原理到实战
后端·golang
qq_396153455 小时前
docker ddns-go 忘记密码
docker·容器·golang
XMYX-05 小时前
04 - Go 的变量和常量:零值、类型推导与枚举
开发语言·golang
好家伙VCC5 小时前
**InfluxDB实战进阶:基于Golang的高性能时序数据采集与可视化方
java·开发语言·后端·python·golang
好家伙VCC5 小时前
**发散创新:基于Go语言的服务网格实践与流量治理实战**在微服务架构日益复杂的今天,**服务网格(S
java·python·微服务·架构·golang
lolo大魔王5 小时前
Go语言的循环语句、判断语句、通道选择语句
开发语言·算法·golang
呆萌很12 小时前
【GO】结构体构造函数练习题
golang
codeejun16 小时前
每日一Go-44、Go网络栈深度拆解--从 TCP 到 HTTP 的资源复用艺术
网络·tcp/ip·golang
GDAL18 小时前
Go Channel `close()` 深入全面讲解
golang·通道·close