无缓冲信道 Channel 是无法保存任何值的,该类型信道要求 发送 goroutine 和 接受 goroutine 两者同时准备好,这样才能完成发送与接受的操作。
1、无缓冲 Channel
前几篇文章我们使用 make 创建出来的信道 Channel,并没有传入第二个参数,也就是说,没有传入第二个参数创建的信道 Channel,便是无缓冲信道咯。
无缓冲信道 Channel 是无法保存任何值的,该类型信道要求 发送 goroutine 和 接受 goroutine 两者同时准备好,这样才能完成发送与接受的操作。
假使 两者 goroutine 未能同时准备好,信道便会先执行 发送 和 接受 的操作, goroutine 会阻塞等待。这种对信道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。
阻塞指的是由于某种原因数据没有到达,当前协程(线程)持续处于等待状态,直到条件满足才解除阻塞。
同步指的是在两个或多个协程(线程)之间,保持数据内容一致性的机制。
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
// wg 用来等待程序结束
var wg sync.WaitGroup
func init() {
rand.Seed(time.Now().UnixNano())
}
func main() {
// 创建一个无缓冲的通道
court := make(chan int)
// 计数加 2,表示要等待两个goroutine
wg.Add(2)
// 启动两个选手
go player("Nadal", court)
go player("Djokovic", court)
// 发球
court <- 1
// 等待游戏结束
wg.Wait()
}
// player 模拟一个选手在打网球
func player(name string, court chan int) {
// 在函数退出时调用Done 来通知main 函数工作已经完成
defer wg.Done()
for {
// 等待球被击打过来
ball, ok := <-court
if !ok {
// 如果通道被关闭,我们就赢了
fmt.Printf("Player %s Won\n", name)
return
}
// 选随机数,然后用这个数来判断我们是否丢球
n := rand.Intn(100)
if n%13 == 0 {
fmt.Printf("Player %s Missed\n", name)
// 关闭通道,表示我们输了
close(court)
return
}
// 显示击球数,并将击球数加1
fmt.Printf("Player %s Hit %d\n", name, ball)
ball++
// 将球打向对手
court <- ball
}
}
2、有缓冲 Channel
有缓冲的 Channel )是一种在被接收前能存储一个或者多个值。这种类型的信道并不强制要求 goroutine 之间必须同时完成发送和接收。信道会阻塞发送和接收动作的条件也会不同。只有在信道中没有要接收的值时,接收动作才会阻塞。只有在信道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。
这导致有缓冲的信道和无缓冲的信道之间的一个很大的不同:无缓冲的信道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的信道没有这种保证。
在无缓冲通道的基础上,为信道增加一个有限大小的存储空间形成带缓冲信道。带缓冲信道在发送时无需等待接收方接收即可完成发送过程,并且不会发生阻塞,只有当存储空间满时才会发生阻塞。同理,如果缓冲通道中有数据,接收时将不会发生阻塞,直到信道中没有数据可读时,信道将会再度阻塞。
varChan := make(chan chanType,chanSize)
//chanSize 决定可以保存多少大小的元素数量
package main
import (
"fmt"
)
func main() {
// 创建一个4个元素缓冲大小的 string 通道
ch := make(chan string, 4)
// 查看当前通道的大小
fmt.Println(len(ch))
// 发送4个 string 元素到通道
ch <- "秋码记录"
ch <- "https://qiucode.cn"
ch <- "你我杂志刊"
ch <- "仗剑行于江湖"
// 查看当前通道的大小
fmt.Println(len(ch))
}
带缓冲信道在很多特性上和无缓冲信道是类似的。无缓冲信道可以看作是长度永远为 0 的带缓冲信道。因此根据这个特性,带缓冲信道在下面列举的情况下依然会发生阻塞:
- 带缓冲信道被填满时,尝试再次发送数据时发生阻塞。
- 带缓冲信道为空时,尝试接收数据时发生阻塞。
为什么Go语言对信道要限制长度而不提供无限长度的信道?
我们知道信道(channel)是在两个 goroutine 间通信的桥梁。使用 goroutine 的代码必然有一方提供数据,一方消费数据。当提供数据一方的数据供给速度大于消费方的数据处理速度时,如果信道不限制长度,那么内存将不断膨胀直到应用崩溃。因此,限制信道的长度有利于约束数据提供方的供给速度,供给数据量必须在消费方处理量+信道长度的范围内,才能正常地处理数据。