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:
- Làm sao khởi chạy đồng thời? →
goroutine(từ khoágo) - Làm sao chờ tất cả xong rồi mới in báo cáo? →
sync.WaitGroup - 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 wg và results:
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ó đóng và rỗ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)