参考资料:
https://www.bilibili.com/video/BV1gf4y1r79E
https://www.runoob.com/w3cnote/go-channel-intro.html
channels
channel是一种数据类型,他能够实现协程之间的数据通信
他有点类似于操作系统IPC的管道通信方式,但是操作系统的管道通信是单向的,而channel可以是双向也可以是单向,并且channel的数据交换是带有基本类型的
跟map和slices一样,channel在使用前必须经过初始化,并且带有一个基本类型(例如int,float,bool)一同作为类型的声明
ch := make(chan int) // 缓冲区为0 即 ch := make(chan int, 0)
ch2 := make(chan int, 100) // 缓冲区为100
创建完毕后,我们就可以使用下面操作符
// goroutine A
ch <- v // 发送值v到Channel ch中
// goroutine B
v := <-ch // 从Channel ch中接收数据,并将数据赋值给v
channelType有下面三种(其中ElementType可以为int,float,bool)
chan ElementType:可以接收和发送 T类型 的数据chan<- ElementType:只可以用来发送 T 类型 的数据<-chan ElementType:只可以用来接收 T 类型 的数据
channel属于生产者与消费者模型,当生产者生产的东西没人消费,就会阻塞;当消费者需要消费却没有东西,同样会阻塞
也就是channel两侧的协程会保持同步,下面介绍有无缓冲区两种情况对应的例子:
无缓冲区
对于无缓冲区的管道(发送和接收必须同时准备好),当协程A向管道写入,协程B读取时:
- 如果A协程运行的慢,还没写入管道;而B协程运行的快,已经从管道另一侧读了。那么由于没有数据可读,B协程会阻塞等待。只有A开始向管道写完后,才会唤起B协程执行后续任务
func main() {
ch := make(chan int)
go func() {
fmt.Println("A: 处理耗时任务中...")
time.Sleep(2 * time.Second)
fmt.Println("A: 准备发送")
ch <- 1
fmt.Println("A: 发送完成")
}()
fmt.Println("B: 准备接收")
x := <-ch
fmt.Println("B: 接收到", x)
time.Sleep(time.Second)
}
- 如果A协程运行的快,已经开始写入管道;而B协程运行的慢,管道另一侧还没读。那么由于数据没有被消费,A协程会阻塞等待。只有B开始从管道消费完后,才会唤起A协程执行后续任务
func main() {
ch := make(chan int)
go func() {
fmt.Println("A: 准备发送")
ch <- 1
fmt.Println("A: 发送完成")
}()
fmt.Println("B: 处理耗时任务中...")
time.Sleep(2 * time.Second)
fmt.Println("B: 准备接收")
x := <-ch
fmt.Println("B: 接收到", x)
time.Sleep(time.Second)
}
有缓冲区
- A发送时,如果缓冲区没满,发送方会把数据放入缓冲区,然后继续执行,不需要等待接收方
- B接收时,如果缓冲区有数据,接收方会直接从缓冲区取出数据,然后继续执行。
func main() {
ch := make(chan int, 2)
go func() {
fmt.Println("A: 准备发送 1")
ch <- 1
fmt.Println("A: 发送 1 完成")
fmt.Println("A: 准备发送 2")
ch <- 2
fmt.Println("A: 发送 2 完成")
}()
time.Sleep(2 * time.Second)
fmt.Println("B: 准备接收")
x := <-ch
fmt.Println("B: 接收到", x)
time.Sleep(time.Second)
}
- 发送时,如果缓冲区已经满了,发送方会阻塞,直到有接收方取走数据,缓冲区空出位置
func main() {
ch := make(chan int, 2)
go func() {
fmt.Println("A: 发送 1")
ch <- 1
fmt.Println("A: 发送 2")
ch <- 2
fmt.Println("A: 发送 3")
// 阻塞直到B读取
ch <- 3
fmt.Println("A: 发送 3 完成")
}()
time.Sleep(2 * time.Second)
fmt.Println("B: 准备接收")
x := <-ch
fmt.Println("B: 接收到", x) // 1
time.Sleep(time.Second)
}
- 接收时,如果缓冲区为空,接收方会阻塞,直到有发送方发送数据。(略)
关闭channel
close(ch)
使用场景:
- 不需要像文件一样每次都显示关闭协程,一般不会手动关闭
- 只有当另一个协程处于循环+阻塞等待时候,当前协程才需要显示关闭,释放资源
注意:
- channel手动关闭后,协程不能写数据,但是可以读数据
- 对于nil的channel,无论怎么收发都会阻塞
当协程中有循环依赖channel返回的结果时候
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("B:已发送: ", i)
}
close(ch) // 必须进行关闭
}()
for {
if data, ok := <-ch; ok {
fmt.Println("A:已接收: ", data)
} else {
break
}
}
fmt.Println("main exit")
}
如果去掉close将会引发panic:fatal error: all goroutines are asleep - deadlock!
有缓冲区,并且缓冲区没有被消费完时,close调channel会不会导致ok值变化?
func main() {
ch := make(chan int, 6)
go func() {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("B:已发送: ", i)
}
close(ch)
}()
time.Sleep(2 * time.Second)
for {
if data, ok := <-ch; ok {
fmt.Println("A:已接收: ", data)
} else {
break
}
}
fmt.Println("main exit")
}为了保证接收前B发送完毕,我们让main睡了2秒,可以看到
ok依旧在缓冲区有值的时候为true
只有缓冲区没有数据的时候,并且channel已经被关闭的清空下,ok才会为false
使用
Range迭代读取数据
for data := range ch{
fmt.Println("A:已接收: ", data)
}
该段代码会不断从ch读取数据,如果没有缓冲区数据并且已经channel已关闭则会退出
select监控多个channel读写状态
select{
case <- chanA:
//如果chanA成功读到数据,则进行该case处理语句
case chanB <- 1:
//如果成功向chanB写入数据,则进行该case处理语句
default:
//如果上面都没有成功,则进入default处理流程
案例:
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch1 <- "接口 A 的结果"
}()
go func() {
time.Sleep(1 * time.Second)
ch2 <- "接口 B 的结果"
}()
select {
case result := <-ch1:
fmt.Println("收到 ch1:", result)
case result := <-ch2:
fmt.Println("收到 ch2:", result)
}
fmt.Println("main 结束")
}
如果同时准备好,那么sleclet会随机选择一个可以执行的case
超时控制
// 创建计时器
timerCh := time.After(2 * time.Second)
执行上面的函数会返回一个 <-chan time.Time 他是一个只读的管道,并且在两秒之后会往 timerCh 里面发送一个时间戳
我们可以搭配 select 做超时控制
func main() {
resultCh := make(chan string)
go func() {
time.Sleep(3 * time.Second)
resultCh <- "任务结果"
}()
select {
case result := <-resultCh:
fmt.Println("收到结果:", result)
case <-time.After(2 * time.Second): // 2s后会读到
fmt.Println("等待超时")
}
}

评论(0)
暂无评论