概念
channel是goroutine(协程)间 通信的一种方式,采用CSP模型,通过数据在不同协程间的流动实现 协程间的合作; 其他协程间通信方式 包括较为常见的 内存共享,信号量等等;
实现原理
channel本质是个结构体,此结构体 维护了 3个队列,具体如下:
- channel的实现原理,本质上就是 协程间对 一个固定长度的环形队列 读写操作;
- 队列满了就不能写入,写入的协程要暂存到 写入队列中;
- 队列空了就不能读取,读取的协程要暂存到 读取队列中;
具体实现细节 点击链接
根据实现原理对应的功能特点
- 读取channel的协程会将数据从环形队列中取出; 造成的效果是 channel中的一个数据只能被一个协程读取;实现了 生产者消费者的模式(不会重复消费);
- 当channel关闭时,会 广播的方式通知所有 读取队列中 的协程,告诉他们 此channel要关闭了; 造成的效果是 通过channel的关闭功能,实现了 协程间的广播模式;
实现如上功能特点的示例代码
package main
import (
"fmt"
"sync"
"time"
)
func main() {
wg := &sync.WaitGroup{}
ch := make(chan int, 2)
send := func() {
for i := 0; i < 10; i++ {
ch <- i
}
time.Sleep(time.Second)
fmt.Println("关闭channel")
close(ch)
}
recv := func(id int) {
defer wg.Done()
for {
//fmt.Printf("进入到消费者协程 %d 的for循环\n", id)
select {
case i, ok := <-ch:
if ok == true {
fmt.Printf("消费者协程 %d 从channel中取到数据为: %d\n", id, i)
} else {
fmt.Printf("广播模式通知 消费者协程 %d 退出\n", id)
return
}
//不加default语句 会阻塞等待
//default:
//fmt.Println("fu")
}
}
}
wg.Add(3)
fmt.Println("生成3个消费者协程")
go recv(0)
go recv(1)
go recv(2)
fmt.Println("生产者发送10条数据到channel")
send()
wg.Wait()
}
注意点
读取channel不同方式的区别
- 通过 for range 方式读取数据,在没有新数据时,会阻塞代码;
for i := range ch {
fmt.Printf("通过 for range方式 阻塞读取channel中的数据,没有新数据可以读取,就阻塞住代码")
}
通过 select case default方式读取 不会阻塞代码;因为 此方式目的是 在一个地方统一管理各个channel,有数据可以读取时就执行对应的case代码; 没有channel数据可读取就执行default; 如果没有default分支,会堵塞代码;
select {
case i, ok := <-ch:
if ok==true {
fmt.Println(i)
}else{
fmt.Printf("channel关闭")
}
case i, ok := <-ch_one:
if ok==true {
fmt.Println(i)
}else{
fmt.Printf("channel关闭")
}
default:
}
相关链接: