chan struct{}传递信号最轻量,因struct{}占0字节内存且语义明确——仅通知事件发生、不传数据;比chan bool/int更安全省资源,常用于goroutine退出、任务完成标记、配置热重载;声明done := make(chan struct{}),发送推荐close(done)。

用 chan struct{} 传递信号最轻量
结构体空类型 struct{} 占用 0 字节内存,且语义明确——只用来通知“发生了某事”,不传数据。比用 chan bool 或 chan int 更安全、更省资源。
常见场景:goroutine 退出通知、任务完成标记、配置热重载触发。
- 声明:
done := make(chan struct{}) - 发送信号:
close(done)(推荐)或done - 接收信号:
<-done(阻塞等待)或select { case <-done: ... default: ... }(非阻塞尝试) - 避免重复 close:
close()只能调一次,多次 panic;若不确定是否已关,可用sync.Once包裹
select 监听多个信号通道时必须配 default 或 time.After
只监听几个 chan struct{} 却没 default,容易卡死——比如某个信号永远不发,整个 goroutine 就挂住不动了。
典型错误写法:
立即学习“go语言免费学习笔记(深入)”;
select {
case <-done:
return
case <-quit:
return
// ❌ 没 default,也没 timeout,万一两个都没发,就永久阻塞
}正确做法:
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 非阻塞轮询:
default分支做兜底逻辑 - 带超时退出:
case <-time.After(5 * time.Second): return - 组合多个信号:
case <-ctx.Done(): return(如果用了 context)
别在接收方关闭 done 通道
信号通道的生命周期应由“事件发起者”控制。比如主流程决定停止 worker,就 close(done);worker 只负责监听,不该反向操作。
错误模式:
- worker 自己
close(done)→ 其他监听者收到后误判为“全局退出” - 多个 goroutine 竞争 close → 第二次 close panic
- 用
chan<- struct{}或<-chan struct{}类型约束收发方向,编译期防错
推荐结构:
func startWorker(done <-chan struct{}) {
for {
select {
case <-done:
return // 安全退出
default:
// 工作逻辑
}
}
}用 nil channel 实现条件性禁用
想临时屏蔽某个信号分支?把对应 channel 设为 nil,select 会直接跳过它——比加 if 判断更干净,也不影响其他 case 执行。
适用场景:动态开关监控、按需启用重试、灰度功能开关。
-
var healthCheck chan struct{},初始为nil - 开启时:
healthCheck = make(chan struct{}) - 关闭时:
healthCheck = nil(不是close()) - select 中写
case <-healthCheck:,healthCheck == nil时该分支永不就绪
注意:nil channel 和已关闭 channel 行为完全不同——前者读写都永久阻塞,后者读返回零值、写 panic。

















