Menu
CoddyTech

缓冲通道与无缓冲通道

本课属于 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) 查看总容量。

各自的使用场景:无缓冲通道可以实现紧密同步:发送方知道接收方已收到值。有缓冲通道可以让发送方和接收方的时间安排彼此独立,适用于生产者和消费者工作速度不同的情况。不过,有缓冲通道可能会掩盖同步错误,因此除非有特定的缓冲理由,否则优先使用无缓冲通道。

challenge icon

挑战

简单

我们来构建一个批处理器,演示带缓冲通道和无缓冲通道的区别。你将创建一个系统,让生产者向处理器发送项目,并使用带缓冲通道来解耦它们的工作时序,使生产者可以提前处理。

你的代码将分布在两个文件中:

  • 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: 将每个已消费的结果分别打印在单独的一行
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Go 在线编译器