# WaitGroup

***

### 🚦 1. `WaitGroup` là gì?

`sync.WaitGroup` là một công cụ đồng bộ hoá trong Go, giúp **đợi cho một nhóm goroutine hoàn thành** công việc trước khi tiếp tục.

> Cực kỳ hữu ích khi bạn **fire nhiều goroutine song song** và cần biết **khi nào tất cả xong**.

***

### 🛠 2. Cách dùng cơ bản

```go
var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1) // tăng counter lên 1
    go func(i int) {
        defer wg.Done() // giảm counter đi 1 khi xong
        fmt.Println("Goroutine", i)
    }(i)
}

wg.Wait() // đợi tất cả goroutines xong
fmt.Println("All goroutines done.")
```

#### Ghi nhớ:

* `Add(n)` = báo là có `n` công việc sẽ đến
* `Done()` = báo là 1 công việc đã xong
* `Wait()` = block cho đến khi counter về 0

***

### ⚠️ 3. Lỗi thường gặp

#### ❌ Quên `Done()` → Deadlock

```go
wg.Add(1)
go func() {
    // quên defer wg.Done()
}()
wg.Wait() // sẽ block mãi mãi
```

#### ❌ Gọi `Add()` sau khi `Wait()` đã bắt đầu

Không thread-safe nếu gọi `Add()` sau khi `Wait()` đang chạy → race condition

✅ Cách đúng: Gọi toàn bộ `Add()` **trước khi** gọi `Wait()`.

***

### 🧠 4. Sử dụng WaitGroup kết hợp với xử lý lỗi (pattern hay dùng)

Bạn có thể kết hợp với channel để collect error:

```go
var wg sync.WaitGroup
errCh := make(chan error, 3) // buffered để không bị block

for _, task := range tasks {
    wg.Add(1)
    go func(t Task) {
        defer wg.Done()
        if err := t.Do(); err != nil {
            errCh <- err
        }
    }(task)
}

wg.Wait()
close(errCh)

for err := range errCh {
    fmt.Println("Error:", err)
}
```

***

### 📦 5. Best Practices từ Senior Dev

| Best Practice                                    | Giải thích                               |
| ------------------------------------------------ | ---------------------------------------- |
| ✅ Dùng `defer wg.Done()` ngay đầu goroutine      | Để tránh quên gọi `Done()` khi panic     |
| ✅ Gọi `Add(n)` trước khi chạy goroutines         | Đảm bảo tính nhất quán                   |
| 🚫 Không gọi `Wait()` trong goroutine            | Có thể gây deadlock nếu không cẩn thận   |
| ✅ Dùng `sync.Once` kết hợp khi cần chỉ gọi 1 lần | Trong trường hợp cần shutdown, hoặc init |

***

### 🧪 6. Use-case thực tế (API Parallel Requests)

Ví dụ gọi song song nhiều API và chờ tất cả hoàn thành:

```go
func fetchAll(urls []string) {
    var wg sync.WaitGroup
    for _, url := range urls {
        wg.Add(1)
        go func(u string) {
            defer wg.Done()
            resp, err := http.Get(u)
            if err != nil {
                log.Println("Error:", err)
                return
            }
            defer resp.Body.Close()
            body, _ := io.ReadAll(resp.Body)
            fmt.Println("Fetched:", u, "Length:", len(body))
        }(url)
    }
    wg.Wait()
    fmt.Println("All done.")
}
```

***

### 🚀 7. Bonus: Tự wrap WaitGroup thành TaskPool

Nếu bạn thích code gọn hơn:

```go
type TaskGroup struct {
    wg sync.WaitGroup
}

func (tg *TaskGroup) Go(fn func()) {
    tg.wg.Add(1)
    go func() {
        defer tg.wg.Done()
        fn()
    }()
}

func (tg *TaskGroup) Wait() {
    tg.wg.Wait()
}
```

Dùng:

```go
var tg TaskGroup

tg.Go(func() {
    doSomething()
})
tg.Go(func() {
    doAnotherThing()
})
tg.Wait()
```

***

### 🔚 Tổng kết

* `sync.WaitGroup` là một công cụ rất mạnh khi làm việc với goroutine.
* Là Senior, bạn cần **hiểu kỹ lifecycle**, tránh **race condition**, và dùng kết hợp `channel`, `context`, `errgroup` (khi cần).
* Go’s concurrency dễ viết nhưng khó debug – nên cần cấu trúc và thói quen viết code rõ ràng, clean.
