Skip to main content

🔄 Băng Chuyền Món Ăn - Channels

Trong nhà hàng hiện đại, băng chuyền (channels) giúp truyền món ăn giữa các bộ phận một cách trơn tru. Trong Go, channels là cách để các goroutines giao tiếp và đồng bộ với nhau.

1. Băng Chuyền Là Gì?

Channel giống như băng chuyền truyền món ăn giữa nhân viên phục vụ và bếp:

package main

import "fmt"

func main() {
// Tạo băng chuyền truyền order
orderChannel := make(chan string)

// Bếp nhận order
go func() {
order := <-orderChannel // Nhận món từ băng chuyền
fmt.Println("Bếp nhận:", order)
}()

// Nhân viên gửi order
orderChannel <- "Phở bò" // Gửi món lên băng chuyền

// Đợi một chút để bếp xử lý
fmt.Scanln()
}

Giải thích:

  • make(chan string) - Tạo băng chuyền truyền món (kiểu string)
  • <- - Toán tử gửi/nhận món
  • orderChannel <- "Phở bò" - Gửi món lên băng chuyền
  • order := <-orderChannel - Nhận món từ băng chuyền

2. Băng Chuyền Có Hướng

Băng chuyền có thể chỉ nhận hoặc chỉ gửi:

package main

import "fmt"

// Nhân viên phục vụ - chỉ gửi order
func waiter(orders chan<- string) {
orders <- "Bún chả"
orders <- "Chả giò"
close(orders) // Đóng băng chuyền khi hết việc
}

// Bếp - chỉ nhận order
func kitchen(orders <-chan string) {
for order := range orders {
fmt.Println("Đang nấu:", order)
}
}

func main() {
orderChannel := make(chan string)

go waiter(orderChannel)
kitchen(orderChannel)
}

Giải thích:

  • chan<- string - Băng chuyền chỉ gửi (send-only)
  • <-chan string - Băng chuyền chỉ nhận (receive-only)
  • close(orders) - Đóng băng chuyền khi không còn món
  • range - Lặp qua tất cả món cho đến khi băng chuyền đóng

3. Băng Chuyền Có Chỗ Chứa (Buffered Channels)

Băng chuyền có thể chứa nhiều món trong hàng đợi:

package main

import "fmt"

func main() {
// Băng chuyền có thể chứa 3 order
orderQueue := make(chan string, 3)

// Gửi 3 order liên tiếp (không bị block)
orderQueue <- "Phở gà"
orderQueue <- "Bún bò"
orderQueue <- "Cơm tấm"

fmt.Println("Đã gửi 3 order vào hàng đợi")

// Nhận từng order
fmt.Println("Order 1:", <-orderQueue)
fmt.Println("Order 2:", <-orderQueue)
fmt.Println("Order 3:", <-orderQueue)
}

Giải thích:

  • make(chan string, 3) - Tạo băng chuyền chứa được 3 món
  • Gửi không bị block cho đến khi băng chuyền đầy
  • Nhận không bị block nếu băng chuyền có món

4. Hệ Thống Order Thực Tế

Mô phỏng hệ thống order hoàn chỉnh:

package main

import (
"fmt"
"time"
)

type Order struct {
ID int
Dish string
Customer string
}

// Nhân viên nhận order
func takeOrders(orders chan<- Order) {
orderList := []Order{
{1, "Phở bò", "Bàn 1"},
{2, "Bún chả", "Bàn 2"},
{3, "Cơm tấm", "Bàn 3"},
}

for _, order := range orderList {
fmt.Printf("📝 Nhận order #%d: %s - %s\n",
order.ID, order.Dish, order.Customer)
orders <- order
time.Sleep(500 * time.Millisecond)
}
close(orders)
}

// Bếp nấu món
func cookOrders(orders <-chan Order, ready chan<- Order) {
for order := range orders {
fmt.Printf("👨‍🍳 Đang nấu #%d: %s\n", order.ID, order.Dish)
time.Sleep(1 * time.Second)
ready <- order
}
close(ready)
}

// Nhân viên giao món
func serveOrders(ready <-chan Order) {
for order := range ready {
fmt.Printf("🍽️ Giao món #%d: %s cho %s\n",
order.ID, order.Dish, order.Customer)
}
}

func main() {
orderChannel := make(chan Order, 2)
readyChannel := make(chan Order, 2)

go takeOrders(orderChannel)
go cookOrders(orderChannel, readyChannel)
serveOrders(readyChannel)
}

5. Select - Chọn Từ Nhiều Băng Chuyền

Nhân viên có thể nhận order từ nhiều nguồn:

package main

import (
"fmt"
"time"
)

func main() {
dineIn := make(chan string)
takeaway := make(chan string)
delivery := make(chan string)

// Order tại chỗ
go func() {
time.Sleep(100 * time.Millisecond)
dineIn <- "Phở bò - Bàn 5"
}()

// Order mang về
go func() {
time.Sleep(200 * time.Millisecond)
takeaway <- "Bún chả - Mang về"
}()

// Order giao hàng
go func() {
time.Sleep(150 * time.Millisecond)
delivery <- "Cơm tấm - Giao 123 Nguyễn Huệ"
}()

// Nhận order từ nguồn nào đến trước
for i := 0; i < 3; i++ {
select {
case order := <-dineIn:
fmt.Println("🏠 Tại chỗ:", order)
case order := <-takeaway:
fmt.Println("📦 Mang về:", order)
case order := <-delivery:
fmt.Println("🚗 Giao hàng:", order)
}
}
}

Giải thích:

  • select - Chọn channel nào sẵn sàng trước
  • Xử lý đồng thời nhiều nguồn order
  • Không bị block khi chờ một channel cụ thể

6. Select Với Default

Xử lý khi không có order:

package main

import (
"fmt"
"time"
)

func main() {
orders := make(chan string, 2)

// Gửi một order
orders <- "Phở gà"

// Kiểm tra order liên tục
for i := 0; i < 5; i++ {
select {
case order := <-orders:
fmt.Println("✅ Có order:", order)
default:
fmt.Println("⏳ Chưa có order, đang chờ...")
}
time.Sleep(500 * time.Millisecond)
}
}

Giải thích:

  • default - Chạy khi không có channel nào sẵn sàng
  • Tránh bị block khi chờ order
  • Hữu ích cho việc polling hoặc xử lý khác

7. Timeout Cho Order

Hủy order nếu chờ quá lâu:

package main

import (
"fmt"
"time"
)

func main() {
orders := make(chan string)

// Bếp chậm
go func() {
time.Sleep(3 * time.Second)
orders <- "Phở bò đặc biệt"
}()

// Chờ tối đa 2 giây
select {
case order := <-orders:
fmt.Println("✅ Món đã sẵn sàng:", order)
case <-time.After(2 * time.Second):
fmt.Println("⏰ Timeout! Khách hàng đã rời đi")
}
}

Giải thích:

  • time.After() - Tạo channel timeout
  • Hủy xử lý nếu mất quá nhiều thời gian
  • Cải thiện trải nghiệm khách hàng

8. Đồng Bộ Với Channels

Đợi tất cả công việc hoàn thành:

package main

import (
"fmt"
"time"
)

func prepareIngredient(name string, done chan<- bool) {
fmt.Printf("🥕 Đang chuẩn bị %s...\n", name)
time.Sleep(1 * time.Second)
fmt.Printf("✅ %s đã sẵn sàng\n", name)
done <- true
}

func main() {
done := make(chan bool)

ingredients := []string{"Thịt bò", "Rau sống", "Nước dùng"}

// Chuẩn bị song song
for _, ingredient := range ingredients {
go prepareIngredient(ingredient, done)
}

// Đợi tất cả nguyên liệu
for range ingredients {
<-done
}

fmt.Println("🍲 Tất cả nguyên liệu đã sẵn sàng! Bắt đầu nấu.")
}

9. Thực Hành Tốt

Nguyên tắc sử dụng channels:

package main

import "fmt"

// ✅ TỐT: Người gửi đóng channel
func goodPractice() {
orders := make(chan string, 3)

go func() {
orders <- "Phở"
orders <- "Bún"
close(orders) // Người gửi đóng
}()

for order := range orders {
fmt.Println(order)
}
}

// ❌ TRÁNH: Gửi vào channel đã đóng
func badPractice() {
orders := make(chan string)
close(orders)
// orders <- "Phở" // Panic!
}

// ✅ TỐT: Kiểm tra channel đã đóng
func checkClosed() {
orders := make(chan string, 1)
orders <- "Phở"
close(orders)

order, ok := <-orders
if ok {
fmt.Println("Nhận được:", order)
}

_, ok = <-orders
if !ok {
fmt.Println("Channel đã đóng")
}
}

func main() {
goodPractice()
checkClosed()
}

Lưu ý quan trọng:

  • ✅ Luôn đóng channel từ phía người gửi
  • ✅ Sử dụng buffered channel khi cần hàng đợi
  • ✅ Dùng select cho nhiều channel
  • ❌ Không gửi vào channel đã đóng
  • ❌ Không đóng channel nhiều lần
  • ❌ Không đóng channel từ phía người nhận

Tóm tắt: Channels là băng chuyền truyền thông tin giữa các goroutines. Chúng giúp đồng bộ hóa và giao tiếp an toàn giữa các phần xử lý đồng thời trong nhà hàng code của bạn.

Tiếp theo: Testing - Kiểm Tra Chất Lượng