Menu
CoddyTech

Channel có bộ đệm và không có bộ đệm

Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 61 trên 107.

Các channel chúng ta đã dùng cho đến nay là không có bộ đệm. Chúng không có sức chứa để lưu giá trị. Thao tác gửi sẽ chặn cho đến khi một goroutine khác nhận, và ngược lại. Channel có bộ đệm bổ sung vùng lưu trữ nội bộ, cho phép thao tác gửi hoàn tất mà không cần có bên nhận ngay lập tức.

Tạo một kênh có bộ đệm bằng cách truyền sức chứa vào make:

// Không có bộ đệm, dung lượng 0
unbuffered := make(chan int)

// Có bộ đệm, dung lượng 3
buffered := make(chan int, 3)

Với kênh có bộ đệm, thao tác gửi chỉ bị chặn khi bộ đệm đầy, còn thao tác nhận chỉ bị chặn khi bộ đệm trống:

ch := make(chan string, 2)

ch <- "first"   // Không bị chặn vì bộ đệm còn chỗ trống
ch <- "second"  // Không bị chặn vì bộ đệm còn chỗ trống
// ch <- "third" sẽ bị chặn vì bộ đệm đã đầy

fmt.Println(<-ch)  // "first"
fmt.Println(<-ch)  // "second"

Dùng len(ch) để kiểm tra hiện có bao nhiêu phần tử trong bộ đệm, và dùng cap(ch) để xem tổng dung lượng.

Khi nào nên dùng từng loại: Kênh không đệm giúp đồng bộ chặt chẽ: bên gửi biết bên nhận đã nhận được giá trị. Kênh có đệm tách biệt thời điểm hoạt động của bên gửi và bên nhận, hữu ích khi bên tạo và bên tiêu thụ hoạt động với tốc độ khác nhau. Tuy nhiên, kênh có đệm có thể che giấu lỗi đồng bộ, vì vậy hãy ưu tiên kênh không đệm trừ khi bạn có lý do cụ thể để dùng bộ đệm.

challenge icon

Thử thách

Dễ

Hãy xây dựng một bộ xử lý theo lô để minh họa sự khác biệt giữa kênh có bộ đệm và kênh không có bộ đệm. Bạn sẽ tạo một hệ thống trong đó bên sản xuất gửi các mục đến bộ xử lý, dùng kênh có bộ đệm để tách biệt thời gian hoạt động của chúng và cho phép bên sản xuất làm việc trước.

Bạn sẽ tổ chức code trong hai tệp:

  • processor.go: Định nghĩa logic xử lý theo lô bằng kênh.

    Tạo một cấu trúc Item có các trường ID (int) và Value (string).

    Triển khai hai hàm:

    • Producer(items []Item, out chan Item) - Gửi từng mục đến kênh đầu ra. Sau khi gửi mỗi mục, in ra: Produced item [ID]. Sau khi gửi hết các mục, in số mục hiện đang chờ trong bộ đệm bằng len(out) theo định dạng: Buffer has [count] items. Sau đó đóng kênh.
    • Consumer(in chan Item) []string - Nhận các mục từ kênh đầu vào và thu thập chúng vào một lát cắt gồm các chuỗi đã định dạng. Với mỗi mục nhận được, chuỗi sẽ có dạng: Consumed: [ID] - [Value]. Trả về lát cắt khi kênh đóng.
  • main.go: Thiết lập kênh có bộ đệm và điều phối bên sản xuất cùng bên tiêu thụ.

    Đọc sức chứa của bộ đệm, sau đó đọc số mục, rồi đọc ID và giá trị của từng mục. Tạo một kênh có bộ đệm với sức chứa đã chỉ định. Chạy Producer trước (không chạy dưới dạng goroutine) để lấp đầy bộ đệm, sau đó chạy Consumer để xử lý tất cả các mục. In mỗi kết quả đã tiêu thụ trên một dòng riêng.

Đầu vào sẽ gồm:

  • Dòng 1: Sức chứa của bộ đệm (số nguyên)
  • Dòng 2: Số mục (số nguyên)
  • Các dòng tiếp theo: Với mỗi mục, gồm hai dòng: ID của mục (số nguyên), rồi đến giá trị của mục (chuỗi)

Ví dụ, với đầu vào:

3
3
1
apple
2
banana
3
cherry

Đầu ra của bạn sẽ là:

Produced item 1
Produced item 2
Produced item 3
Buffer has 3 items
Consumed: 1 - apple
Consumed: 2 - banana
Consumed: 3 - cherry

Lưu ý rằng với sức chứa bộ đệm là 3, bên sản xuất có thể gửi cả 3 mục mà không bị chặn, và bộ đệm hiển thị 3 mục đang chờ trước khi bên tiêu thụ bắt đầu. Nếu bộ đệm nhỏ hơn số mục, bên sản xuất sẽ bị chặn để chờ có chỗ trống.

Tự làm thử

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Đọc dung lượng bộ đệm
	line, _ := reader.ReadString('\n')
	bufferCapacity, _ := strconv.Atoi(strings.TrimSpace(line))

	// Đọc số lượng phần tử
	line, _ = reader.ReadString('\n')
	numItems, _ := strconv.Atoi(strings.TrimSpace(line))

	// Đọc ID và giá trị của từng phần tử
	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: Tạo channel có bộ đệm với dung lượng đã chỉ định

	// TODO: Chạy Producer (không chạy dưới dạng goroutine) để lấp đầy bộ đệm

	// TODO: Chạy Consumer để xử lý tất cả phần tử

	// TODO: In mỗi kết quả đã tiêu thụ trên một dòng riêng
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Go online