KHOÁ HỌC Phần 1 — Nền tảng Chạy song song — goroutine, WaitGroup, channel

Chạy song song — goroutine, WaitGroup, channel

🧠 Suy nghĩ

"Cả 3 server không liên quan gì đến nhau, sao phải đợi từng cái? Chạy đồng thời đi!"

Nhưng đồng thời sẽ nảy sinh 3 vấn đề mới:

  1. Làm sao khởi chạy đồng thời? → goroutine (từ khoá go)
  2. Làm sao chờ tất cả xong rồi mới in báo cáo? → sync.WaitGroup
  3. Làm sao thu thập kết quả an toàn từ 3 luồng? → channel

📐 Logic (rất quan trọng — đọc kỹ)

Ẩn dụ để hiểu: Bạn là quản lý, thuê 3 nhân viên (goroutine) đi khảo sát 3 nơi:

  • WaitGroup = "danh sách chấm công" — bạn ghi "có 3 người đang làm", ai xong tự gạch tên. Bạn chờ đến khi danh sách trống.
  • Channel = "băng chuyền" — nhân viên báo cáo bằng cách đặt kết quả lên băng chuyền, bạn đứng cuối băng chuyền đọc.

💻 Code

Sửa hàm — thêm wgresults:

func checkServerHealth(wg *sync.WaitGroup, s *Server, results chan<- string, notifier Notifier) {
    defer wg.Done() // ⚠️ QUAN TRỌNG: dù thành công hay lỗi, cũng phải "chấm công xong"

    time.Sleep(1 * time.Second)
    s.SetStatus(true)
    results <- fmt.Sprintf("✅ %s (%s) OK", s.Name, s.IP)
}

Sửa main():

var wg sync.WaitGroup
results := make(chan string, len(servers)) // channel có buffer

for _, srv := range servers {
    wg.Add(1) // ⚠️ PHẢI Add TRƯỚC khi go, không được Add trong goroutine
    go checkServerHealth(&wg, srv, results, alertSystem)
}

wg.Wait()      // Chờ cả 3 goroutine gọi wg.Done()
close(results) // Đóng channel — báo "hết dữ liệu"

// Đọc kết quả
fmt.Println("\n--- BÁO CÁO ---")
for res := range results {
    fmt.Println(res)
}

🔍 Giải thích sâu từng phần

go checkServerHealth(...)

Từ khoá go = chạy ngầm. Hàm main không đợi, chạy tiếp ngay.

sync.WaitGroup

wg.Add(1)   → Tăng bộ đếm lên 1     (Trước khi chạy goroutine)
wg.Done()   → Giảm bộ đếm đi 1      (Khi goroutine chạy xong)
wg.Wait()   → Chặn cho đến khi đếm = 0

Vì sao defer wg.Done()? defer = "hoãn lại đến khi hàm thoát". Đảm bảo dù hàm panic hay return sớm, Done vẫn được gọi → không bao giờ kẹt Wait() mãi mãi.

chan<- string (mũi tên hướng vào)

Nghĩa là "chỉ được gửi vào, không được đọc từ" → tránh lỗi lập trình.

make(chan string, len(servers)) — buffer

  • Không buffer → gửi phải có người đọc ngay, nếu không treo.
  • Có buffer len(servers) → cho phép 3 goroutine gửi xong rồi thoát, không cần đợi ai đọc.

close(results)

Bắt buộc sau wg.Wait(). Vì sao? Nếu đóng khi vẫn còn goroutine gửi → panic.

for res := range results

Đọc channel đến khi nó đóngrỗng. Nếu không close, vòng lặp này sẽ treo mãi.

⏱️ Kết quả

Tổng thời gian: 1 giây (thay vì 3)
← Bản đơn giản — make it work trước