Channel và giao tiếp
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 60 trên 107.
Channels là cơ chế chính của Go để các goroutine giao tiếp với nhau. Thay vì chia sẻ bộ nhớ trực tiếp, các goroutine gửi và nhận giá trị thông qua channel, theo triết lý của Go: "Đừng giao tiếp bằng cách chia sẻ bộ nhớ; hãy chia sẻ bộ nhớ bằng cách giao tiếp."
Tạo một kênh bằng hàm make với từ khóa chan:
messages := make(chan string)Dùng toán tử mũi tên <- để gửi và nhận giá trị. Mũi tên chỉ hướng luồng dữ liệu:
func main() {
messages := make(chan string)
go func() {
messages <- "hello" // gửi vào kênh
}()
msg := <-messages // nhận từ kênh
fmt.Println(msg) // Đầu ra: hello
}Theo mặc định, việc gửi và nhận sẽ chặn cho đến khi phía bên kia sẵn sàng. Khi goroutine gửi "hello", nó chờ cho đến khi main sẵn sàng nhận. Cách chặn này cung cấp cơ chế đồng bộ hóa tích hợp sẵn: không cần time.Sleep.
Bạn có thể đóng một kênh để báo hiệu rằng sẽ không có thêm giá trị nào được gửi. Bên nhận có thể phát hiện điều này:
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}Kênh có kiểu dữ liệu cụ thể: chan string chỉ có thể truyền chuỗi. Tính an toàn kiểu dữ liệu này giúp các goroutine giao tiếp bằng đúng kiểu dữ liệu, phát hiện lỗi khi biên dịch thay vì khi chạy chương trình.
Thử thách
DễHãy xây dựng một hệ thống chuyển tiếp thông điệp để minh họa cách các goroutine giao tiếp qua channel. Bạn sẽ tạo một quy trình xử lý, trong đó thông điệp luân chuyển giữa các giai đoạn xử lý khác nhau, mỗi giai đoạn chạy đồng thời.
Bạn sẽ sắp xếp code trong hai tệp:
relay.go: Định nghĩa quy trình xử lý thông điệp.Tạo một struct
Messagecó các trườngID(int) vàContent(string).Triển khai ba hàm đại diện cho các giai đoạn khác nhau của quy trình xử lý:
Producer(messages []Message, out chan Message)- Nhận một lát cắt thông điệp và gửi từng thông điệp vào channel đầu ra. Sau khi gửi tất cả thông điệp, hãy đóng channel để báo hiệu đã hoàn tất.Transformer(in chan Message, out chan Message)- Nhận thông điệp từ channel đầu vào, chuyển nội dung của từng thông điệp thành chữ hoa, rồi gửi thông điệp đã chuyển đổi vào channel đầu ra. Khi channel đầu vào đóng lại, được phát hiện bằng cú pháp nhận hai giá trị, hãy đóng channel đầu ra.Consumer(in chan Message) []string- Nhận tất cả thông điệp từ channel đầu vào và tập hợp chúng thành một lát cắt gồm các chuỗi đã định dạng. Mỗi chuỗi phải theo định dạng:Message [ID]: [Content]. Trả về lát cắt khi channel đóng.
main.go: Thiết lập quy trình xử lý và điều phối luồng thông điệp đồng thời.Đọc số lượng thông điệp, sau đó đọc ID và nội dung của từng thông điệp. Tạo các thông điệp và thiết lập hai channel để kết nối ba giai đoạn trong quy trình. Chạy Producer và Transformer dưới dạng goroutine, sau đó chạy Consumer trong goroutine chính để thu thập kết quả. In mỗi kết quả trên một dòng riêng.
Đầu vào sẽ có định dạng như sau:
- Dòng 1: Số lượng thông điệp (số nguyên)
- Các dòng tiếp theo: Với mỗi thông điệp, hai dòng: ID thông điệp (số nguyên), rồi đến nội dung (chuỗi)
Ví dụ, với đầu vào:
2
1
hello world
2
go channelsĐầu ra cần có là:
Message 1: HELLO WORLD
Message 2: GO CHANNELSQuy trình hoạt động như sau: Producer gửi thông điệp → Transformer chuyển thành chữ hoa → Consumer thu thập và định dạng kết quả. Mỗi mũi tên biểu thị một channel kết nối các giai đoạn chạy đồng thời. Dùng package strings để chuyển chữ hoa.
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Đọc số lượng thông điệp
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// Đọc các thông điệp
messages := make([]Message, n)
for i := 0; i < n; i++ {
idLine, _ := reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(idLine))
contentLine, _ := reader.ReadString('\n')
content := strings.TrimSpace(contentLine)
messages[i] = Message{ID: id, Content: content}
}
// TODO: Tạo hai kênh để kết nối các giai đoạn của pipeline
// TODO: Khởi chạy Producer dưới dạng goroutine
// TODO: Khởi chạy Transformer dưới dạng goroutine
// TODO: Chạy Consumer trong goroutine chính và thu thập kết quả
// TODO: In mỗi kết quả 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