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.
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
Itemcó các trườngID(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ằnglen(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 - cherryLư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
}
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
1Nền tảng OOP trong Go
Tệp bên ngoàiWorkspace và module trong GoPackage và importTên được export và không được exportGiới thiệu về OOP trong GoStruct như lớpĐịnh nghĩa phương thức cho structReceiver kiểu con trỏ và kiểu giá trịKhởi tạo structHàm khởi tạoÔn tập: máy tính đơn giản4Interface
Giới thiệu về interfaceTriển khai ngầm địnhInterface như một hợp đồngInterface rỗng (any)Khẳng định kiểuswitch kiểuKết hợp interfaceInterface Stringer và ErrorÔn tập: Máy tính hình học7Tính đóng gói
Trường được xuất và không được xuấtĐóng gói ở cấp packagePhương thức getter và setterẨn thông tin trong GoÔn tập: hồ sơ học sinh10Generics (Go 1.18 trở lên)
Giới thiệu về GenericsTham số kiểuRàng buộc kiểuStruct tổng quátGiải pháp thay thế cho phương thức tổng quátÔn tập: tập hợp tổng quát13Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu Abstract FactoryMẫu ObserverMẫu Strategy2Tìm hiểu chuyên sâu về kiểu dữ liệu và struct
Kiểu cơ bản và kiểu tổng hợpĐịnh nghĩa kiểu dữ liệu tùy chỉnhThẻ structStruct ẩn danhStruct lồng nhauGiá trị zero và giá trị mặc địnhÔn tập: sổ liên lạc5Ưu tiên kết hợp hơn kế thừa
Vì sao Go không có kế thừaNhập môn nhúng structTự động nâng phương thứcNhúng nhiều structNhúng và kết tậpChe khuất phương thức được nhúngÔn tập: hệ phân cấp nhân viên8Xử lý lỗi và lập trình hướng đối tượng
Giao diện errorCác kiểu lỗi tùy chỉnhBọc lỗi với fmt.ErrorfLỗi sentinelerrors.Is() và errors.As()Panic, defer và recoverÔn tập: trình phân tích tệp3Con trỏ và bộ nhớ
Cơ bản về con trỏ trong GoCon trỏ đến structTruyền theo giá trị và tham chiếuHàm new()Thu gom rác trong GoÔn tập: xây dựng danh sách liên kết6Đa hình trong Go
Đa hình qua interfaceĐịnh kiểu vịt trong GoQuy tắc thỏa mãn interfaceCác tập hợp đa hìnhTiêm phụ thuộcÔn tập: bộ xử lý thanh toán9Lập trình đồng thời và lập trình hướng đối tượng
Cơ bản về goroutineChannel và giao tiếpChannel có bộ đệm và không có bộ đệmCâu lệnh selectsync.Mutex và sync.RWMutexsync.WaitGroupThiết kế struct an toàn luồngÔn tập: worker poolTự luyện tập: Trình biên dịch Go online