Go 语言中的带有缓冲 Channel(Let‘s Go 三十一)

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 的代码必然有一方提供数据,一方消费数据。当提供数据一方的数据供给速度大于消费方的数据处理速度时,如果信道不限制长度,那么内存将不断膨胀直到应用崩溃。因此,限制信道的长度有利于约束数据提供方的供给速度,供给数据量必须在消费方处理量+信道长度的范围内,才能正常地处理数据。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

甄齐才

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值