KHOÁ HỌC Phần 2 — 20% còn lại Concurrency nâng cao

Concurrency nâng cao

Chương dài nhất Phần 2 — đúng vì concurrency là thế mạnh cốt lõi của Go. Đọc từng mục, không cần 1 lượt.

E1. Mutex — Chia sẻ trạng thái an toàn

Bài toán: nhiều goroutine cùng đếm

var counter int
var mu sync.Mutex

func inc() {
    mu.Lock()
    defer mu.Unlock()
    counter++
}

RWMutex — nhiều đọc, một ghi

var mu sync.RWMutex

func read() {
    mu.RLock()  // Nhiều goroutine cùng RLock được
    defer mu.RUnlock()
    // đọc
}

func write() {
    mu.Lock()   // Ghi độc quyền
    defer mu.Unlock()
    // ghi
}

🎯 Nguyên tắc vàng 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."

→ Ưu tiên channel trước, mutex chỉ khi channel không phù hợp (thường là cache, counter).

🔍 -race — công cụ quan trọng nhất để tự bắt race condition

Race condition (2 goroutine cùng đọc/ghi 1 biến mà không đồng bộ) là loại lỗi khó thấy nhất — chương trình vẫn chạy, vẫn ra kết quả, chỉ thỉnh thoảng sai, cực khó tái hiện bằng mắt thường. Go có sẵn công cụ tự động phát hiện, không cần đoán:

go run -race main.go
go test -race ./...

Cờ -race bật race detector — Go tự theo dõi mọi lần đọc/ghi biến chia sẻ giữa các goroutine lúc chạy, in ra cảnh báo WARNING: DATA RACE kèm đúng dòng code gây ra, ngay cả khi race đó chưa từng gây sai kết quả quan sát được. Nên bật -race khi test bất kỳ code nào có go/goroutine/channel/Mutex — chạy thường không phát hiện được gì, chỉ khi có -race mới thấy.

E2. sync.Once, sync.Map, atomic

sync.Once — khởi tạo 1 lần duy nhất (thread-safe singleton)

var once sync.Once
var db *DB

func GetDB() *DB {
    once.Do(func() {
        db = connectDB()
    })
    return db
}

sync.Map — map an toàn sẵn cho concurrency

var m sync.Map
m.Store("key", 42)
v, ok := m.Load("key")
m.Delete("key")
m.Range(func(k, v any) bool { ... ; return true })

👉 Chỉ nhanh khi: nhiều goroutine đọc/ghi các key khác nhau. Nếu tranh chấp cùng key → dùng map + Mutex tốt hơn.

atomic — thao tác nguyên tử, nhanh hơn Mutex

var counter int64
atomic.AddInt64(&counter, 1)
v := atomic.LoadInt64(&counter)

E3. Channel Patterns kinh điển

🔹 Worker Pool

Nhiều worker cùng xử lý job từ 1 hàng đợi:

jobs := make(chan int, 100)
results := make(chan int, 100)

// 3 worker
for w := 1; w <= 3; w++ {
    go func(id int) {
        for j := range jobs {
            results <- j * 2
        }
    }(w)
}

// Gửi job
for j := 1; j <= 10; j++ { jobs <- j }
close(jobs)

// Đọc kết quả
for i := 0; i < 10; i++ { fmt.Println(<-results) }

🔹 Fan-out — 1 nguồn → nhiều consumer

Chia tải: 1 producer gửi vào channel, N goroutine cùng đọc.

🔹 Fan-in — Nhiều nguồn → 1 consumer

Gộp nhiều channel về 1:

func fanIn(chs ...<-chan int) <-chan int {
    out := make(chan int)
    var wg sync.WaitGroup
    for _, c := range chs {
        wg.Add(1)
        go func(c <-chan int) {
            defer wg.Done()
            for v := range c { out <- v }
        }(c)
    }
    go func() { wg.Wait(); close(out) }()
    return out
}

🔹 Pipeline — Chuỗi xử lý

Stage 1: sinh số → Stage 2: bình phương → Stage 3: cộng dồn. Mỗi stage là 1 goroutine + channel kết nối stage trước với stage sau.

E4. select với default — Non-blocking

select {
case v := <-ch:
    fmt.Println("nhận:", v)
default:
    fmt.Println("không có dữ liệu, không đợi")
}

Timeout ngắn gọn

select {
case v := <-ch:
    // xử lý
case <-time.After(1 * time.Second):
    fmt.Println("timeout")
}

E5. Context — Đầy đủ hơn

context.WithCancel — huỷ chủ động

ctx, cancel := context.WithCancel(context.Background())

go worker(ctx)

time.Sleep(3 * time.Second)
cancel() // Ra lệnh dừng

context.WithDeadline — hạn chót cụ thể

deadline := time.Now().Add(10 * time.Minute)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()

context.WithValue — mang metadata (dùng thận trọng)

ctx := context.WithValue(context.Background(), "userID", 42)

// Trong hàm sâu bên trong:
if v := ctx.Value("userID"); v != nil {
    userID := v.(int)
}

👉 Chỉ dùng cho request-scoped data (trace ID, user ID). Không dùng để truyền tham số bình thường.

← Error handling nâng cao