缓冲通道与无缓冲通道
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 61 课,共 107 课。
到目前为止,我们使用的通道都是无缓冲的。它们没有存储值的容量。发送操作会一直阻塞,直到另一个 goroutine 接收,反之亦然。带缓冲的通道会增加内部存储空间,让发送操作无需立即有接收方也能完成。
通过向 make 传入容量来创建一个带缓冲的通道:
// 无缓冲:容量为 0
unbuffered := make(chan int)
// 有缓冲:容量为 3
buffered := make(chan int, 3)对于带缓冲的通道,只有缓冲区已满时,发送才会阻塞;只有缓冲区为空时,接收才会阻塞:
ch := make(chan string, 2)
ch <- "first" // 不会阻塞,缓冲区还有空间
ch <- "second" // 不会阻塞,缓冲区还有空间
// ch <- "third" 会阻塞,缓冲区已满
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"使用 len(ch) 检查缓冲区中当前有多少项,使用 cap(ch) 查看总容量。
各自的使用场景:无缓冲通道可以实现紧密同步:发送方知道接收方已收到值。有缓冲通道可以让发送方和接收方的时间安排彼此独立,适用于生产者和消费者工作速度不同的情况。不过,有缓冲通道可能会掩盖同步错误,因此除非有特定的缓冲理由,否则优先使用无缓冲通道。
挑战
简单我们来构建一个批处理器,演示带缓冲通道和无缓冲通道的区别。你将创建一个系统,让生产者向处理器发送项目,并使用带缓冲通道来解耦它们的工作时序,使生产者可以提前处理。
你的代码将分布在两个文件中:
processor.go:使用通道定义批处理逻辑。创建一个
Item结构体,包含ID(int)和Value(string)字段。实现两个函数:
Producer(items []Item, out chan Item):将每个项目发送到输出通道。每发送一个项目后,打印:Produced item [ID]。发送完所有项目后,使用len(out)获取缓冲区中当前等待的项目数,并按以下格式打印:Buffer has [count] items。然后关闭通道。Consumer(in chan Item) []string:从输入通道接收项目,并将它们收集到一个由格式化字符串组成的切片中。每个接收到的项目对应的字符串应为:Consumed: [ID] - [Value]。通道关闭后,返回这个切片。
main.go:设置带缓冲通道,并协调生产者和消费者。读取缓冲区容量,然后读取项目数,接着读取每个项目的 ID 和值。创建一个指定容量的带缓冲通道。先运行 Producer(不是作为 goroutine)来填充缓冲区,然后运行 Consumer 处理所有项目。将每个消费结果单独打印一行。
输入内容如下:
- 第 1 行:缓冲区容量(整数)
- 第 2 行:项目数(整数)
- 后续各行:每个项目占两行,先是项目 ID(整数),然后是它的值(字符串)
例如,给定以下输入:
3
3
1
apple
2
banana
3
cherry输出应为:
Produced item 1
Produced item 2
Produced item 3
Buffer has 3 items
Consumed: 1 - apple
Consumed: 2 - banana
Consumed: 3 - cherry请注意,缓冲区容量为 3 时,生产者可以不受阻塞地发送全部 3 个项目,并且在消费者开始运行之前,缓冲区中会显示有 3 个项目等待。如果缓冲区容量小于项目数,生产者就会因等待空位而阻塞。
亲手试试
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 读取缓冲区容量
line, _ := reader.ReadString('\n')
bufferCapacity, _ := strconv.Atoi(strings.TrimSpace(line))
// 读取项目数量
line, _ = reader.ReadString('\n')
numItems, _ := strconv.Atoi(strings.TrimSpace(line))
// 读取每个项目的 ID 和值
items := make([]Item, numItems)
for i := 0; i < numItems; i++ {
line, _ = reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(line))
line, _ = reader.ReadString('\n')
value := strings.TrimSpace(line)
items[i] = Item{ID: id, Value: value}
}
// TODO: 创建一个具有指定容量的带缓冲通道
// TODO: 运行 Producer(不要作为 goroutine 运行)以填满缓冲区
// TODO: 运行 Consumer 处理所有项目
// TODO: 将每个已消费的结果分别打印在单独的一行
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器