当前位置: 首页 > news >正文

go channel 的用法和核心原理、使用场景

一、Channel 的核心用法

1. 基本操作

// 创建无缓冲 Channel(同步通信)
ch := make(chan int)

// 创建有缓冲 Channel(容量为5,异步通信)
bufferedCh := make(chan int, 5)

// 发送数据到 Channel
ch <- 42

// 从 Channel 接收数据
value := <-ch

// 关闭 Channel(只能由发送方关闭)
close(ch)

2. 单向 Channel(类型安全)

// 只写 Channel
func producer(ch chan<- int) {
ch <- 1
}

// 只读 Channel
func consumer(ch <-chan int) {
fmt.Println(<-ch)
}

3. 多路复用(select

select {
case v := <-ch1:
fmt.Println(v)
case ch2 <- 42:
fmt.Println(“sent”)
case <-time.After(time.Second):
fmt.Println(“timeout”)
default:
fmt.Println(“no activity”)
}

4. 遍历 Channel

// 自动检测 Channel 关闭
for v := range ch {
fmt.Println(v)
}


二、Channel 的核心原理

1. 底层数据结构

Channel 在运行时由 hchan 结构体表示(简化版):

type hchan struct {
qcount uint // 当前元素数量
dataqsiz uint // 缓冲区大小(容量)
buf unsafe.Pointer // 环形缓冲区指针
elemsize uint16 // 元素类型大小
closed uint32 // 关闭标志
sendx uint // 发送索引
recvx uint // 接收索引
recvq waitq // 接收等待队列(sudog链表)
sendq waitq // 发送等待队列(sudog链表)
lock mutex // 互斥锁
}

2. 操作流程
  • 发送数据
    • 缓冲区有空位:直接写入缓冲区。
    • 缓冲区已满:当前 Goroutine 被加入 sendq 队列并阻塞(gopark)。
    • 有等待的接收者:直接将数据拷贝到接收方,唤醒接收者(goready)。
  • 接收数据
    • 缓冲区有数据:直接读取。
    • 缓冲区为空:当前 Goroutine 被加入 recvq 队列并阻塞。
    • 有等待的发送者:直接从发送者拷贝数据,唤醒发送者。
3. 关键机制
  • 同步(无缓冲):发送和接收必须同时就绪,否则阻塞。
  • 异步(有缓冲):缓冲区未满/非空时操作立即完成。
  • 关闭 Channel
    • 关闭后发送操作会触发 panic
    • 接收操作会立即返回剩余数据,之后返回零值。
  • Goroutine 调度:通过 goparkgoready 实现阻塞和唤醒。

三、Channel 的使用场景

1. 任务分发与结果收集

// Worker Pool 模式
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
results <- j * 2
}
}

func main() {
jobs := make(chan int, 10)
results := make(chan int, 10)

// 启动3个 Worker
for w := 1; w <= 3; w++ {
    go worker(w, jobs, results)
}

// 分发任务
for j := 1; j <= 5; j++ {
    jobs <- j
}
close(jobs)

// 收集结果
for i := 1; i <= 5; i++ {
    fmt.Println(<-results)
}

}

2. 事件通知

// 使用关闭 Channel 广播事件
var done = make(chan struct{})

func worker() {
for {
select {
case <-done:
fmt.Println(“exit”)
return
default:
// 正常工作
}
}
}

// 关闭 Channel 通知所有 Worker 退出
close(done)

3. 并发控制(信号量)

// 限制并发数为3
var sem = make(chan struct{}, 3)

func task() {
sem <- struct{}{} // 获取信号量
defer func() { <-sem }() // 释放信号量

// 执行任务

}

4. 数据流水线

// 流水线处理:生成 → 平方 → 输出
func gen(nums …int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}

func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}

func main() {
// 流水线连接
c := gen(2, 3)
out := sq©
for n := range out {
fmt.Println(n) // 4, 9
}
}


四、注意事项

  1. 关闭 Channel
    • 只有发送方可以关闭 Channel。
    • 重复关闭会触发 panic
  2. 阻塞与死锁
    • 确保有 Goroutine 接收发送的数据。
  3. 零值 Channel
    • nil Channel 接收或发送会永久阻塞。
  4. 性能优化
    • 小对象直接传递,大对象传递指针。
    • 避免频繁创建和销毁 Channel。

相关文章:

  • JUC并发—10.锁优化与锁故障
  • Spring Boot (maven)分页4.0.2版本 专业版- 模板化最终版(测试)
  • java多线程及线程池
  • Web自动化中Selenium下Chrome与Edge的Webdriver常用Options参数
  • 鸿蒙app 开发中 对于数组方法 filter 的理解
  • 洛谷B3629
  • C#初级教程(7)——初级期末检测
  • 代码随想录第十六天|二叉树part05--654.最大二叉树、617.合并二叉树、700.二叉搜索树中的搜索、98.验证二叉搜索树
  • 前端面试-网络协议篇
  • 创建监听器报错“一个或多个listeners启动失败”
  • Java之泛型
  • Windows安装MySQL指南
  • SAP on Microsoft Azure Architecture and Administration (Ravi Kashyap)
  • LangChain系列:精通LangChain的合并文档链
  • 【论文阅读】SAM-CP:将SAM与组合提示结合起来的多功能分割
  • LeetCode 236.二叉树的最近公共祖先
  • 抗辐照加固CAN FD芯片的商业航天与车规级应用解析
  • 常用Web性能指标
  • idea安装硅基流动中免费的deepseek(2025)
  • C++标准库——move和forward
  • 巴方:印度上周导弹袭击造成至少40名平民死亡
  • 习近平致电祝贺阿尔巴尼斯当选连任澳大利亚总理
  • AI观察|从万元到百万元,DeepSeek一体机江湖混战
  • 江西省市场监管局原局长谢来发被双开:违规接受旅游活动安排
  • 退休10年后,70岁成都高新区管委会原巡视员王晋成被查
  • 80后莆田市文旅局长马骏登台与杨宗纬合唱,“演唱会秒变旅游推介会”