Back to blog

Go Phase 3: Concurrency, HTTP & Advanced Patterns

gogolangprogrammingbackendconcurrency
Go Phase 3: Concurrency, HTTP & Advanced Patterns

Chào mừng đến Phase 3 của Go Learning Roadmap — phase cuối cùng! Trong Phase 1 bạn đã học syntax của Go, và trong Phase 2 bạn đã nắm vững structs, interfaces, error handling, và packages. Giờ là lúc học thứ làm Go nổi tiếng: concurrency.

Concurrency model của Go là vũ khí lợi hại nhất — goroutines và channels cho phép bạn viết concurrent code mà vẫn đọc được. Ngoài concurrency, phase này còn cover context package, testing, HTTP, và build một REST API nhỏ.

Bạn Sẽ Học Được Gì

✅ Launch goroutines và hiểu Go scheduler hoạt động như thế nào
✅ Giao tiếp giữa các goroutines với channels (buffered + unbuffered)
✅ Multiplex channels bằng select
✅ Phối hợp goroutines với sync.WaitGroup, sync.Mutex, sync.Once
✅ Dùng context để cancel và timeout
✅ Viết tests với go test — table-driven, subtests, benchmarks
✅ Build HTTP servers và clients với net/http
✅ Encode/decode JSON
✅ Build một REST API đơn giản từ đầu


Part 1: Goroutines

Go Scheduler Hoạt Động Ra Sao

Goroutine là một lightweight thread do Go runtime quản lý — không phải OS thread. Bạn có thể chạy hàng nghìn goroutines trên chỉ một vài OS threads. Go scheduler (dạng M:N, work-stealing) ghép goroutines vào các CPU đang rảnh.

// Launch một goroutine bằng từ khóa `go`
package main
 
import (
    "fmt"
    "time"
)
 
func sayHello(name string) {
    fmt.Printf("Xin chào, %s!\n", name)
}
 
func main() {
    go sayHello("Alice") // chạy đồng thời
    go sayHello("Bob")   // chạy đồng thời
    go sayHello("Carol") // chạy đồng thời
 
    // Không có cái này, main() có thể kết thúc trước khi goroutines chạy
    time.Sleep(10 * time.Millisecond)
}

Chỉ cần từ khóa go là xong. Nhưng time.Sleep là cách làm tạm bợ — trong code thực tế, bạn dùng channels hoặc sync.WaitGroup để phối hợp.


sync.WaitGroup — Chờ Tất Cả Goroutines

WaitGroup cho phép goroutine chính chờ một tập goroutines hoàn thành:

package main
 
import (
    "fmt"
    "sync"
)
 
func processItem(id int, wg *sync.WaitGroup) {
    defer wg.Done() // báo hiệu xong khi hàm return
    fmt.Printf("Đang xử lý item %d\n", id)
}
 
func main() {
    var wg sync.WaitGroup
 
    for i := 1; i <= 5; i++ {
        wg.Add(1)               // đăng ký thêm một goroutine
        go processItem(i, &wg) // launch goroutine
    }
 
    wg.Wait() // block cho đến khi tất cả gọi Done()
    fmt.Println("Đã xử lý xong tất cả items")
}

Quy tắc:

  • Gọi wg.Add(1) trước khi launch goroutine (không phải bên trong)
  • Dùng defer wg.Done() làm dòng đầu tiên trong goroutine function
  • Truyền *sync.WaitGroup (pointer), không bao giờ copy nó

Part 2: Channels

Channels là cách để các goroutines giao tiếp an toàn với nhau. Triết lý của Go: "Đừng chia sẻ memory để giao tiếp; hãy giao tiếp để chia sẻ memory."

Unbuffered Channels

Unbuffered channel sẽ block sender cho đến khi receiver sẵn sàng (và ngược lại):

package main
 
import "fmt"
 
func sum(nums []int, ch chan int) {
    total := 0
    for _, n := range nums {
        total += n
    }
    ch <- total // gửi vào channel
}
 
func main() {
    nums := []int{1, 2, 3, 4, 5, 6, 7, 8}
    ch := make(chan int)
 
    // Hai goroutines mỗi cái tính tổng nửa slice
    go sum(nums[:4], ch)
    go sum(nums[4:], ch)
 
    // Nhận hai giá trị (block cho đến khi mỗi giá trị tới)
    a, b := <-ch, <-ch
    fmt.Println("Tổng:", a+b) // 36
}

Buffered Channels

Buffered channel có capacity — sends chỉ block khi buffer đầy:

// Buffered channel với capacity 3
ch := make(chan string, 3)
 
ch <- "first"  // không block
ch <- "second" // không block
ch <- "third"  // không block
// ch <- "fourth" // sẽ block — buffer đầy rồi
 
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"
fmt.Println(<-ch) // "third"

Dùng buffered channels khi bạn biết số lượng items trước, hoặc khi muốn tách tốc độ producer khỏi consumer.


Range Over Channels

Bạn có thể range over một channel để nhận values cho đến khi nó bị đóng:

package main
 
import "fmt"
 
func producer(ch chan<- int) {
    // chan<- nghĩa là send-only
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch) // báo hiệu: không có thêm values nữa
}
 
func main() {
    ch := make(chan int)
    go producer(ch)
 
    for v := range ch { // nhận cho đến khi channel đóng
        fmt.Println(v)  // 0, 1, 2, 3, 4
    }
}

Quan trọng: Chỉ sender mới nên đóng channel. Đóng channel hai lần sẽ panic. Gửi vào channel đã đóng cũng panic.


Directional Channels

Channels có thể được type là send-only (chan<-) hoặc receive-only (<-chan):

// Tham số send-only — chỉ có thể gửi vào channel này
func producer(out chan<- string) {
    out <- "hello"
    out <- "world"
    close(out)
}
 
// Tham số receive-only — chỉ có thể nhận từ channel này
func consumer(in <-chan string) {
    for msg := range in {
        fmt.Println(msg)
    }
}
 
func main() {
    ch := make(chan string, 2)
    go producer(ch) // bidirectional ch tự động convert thành chan<-
    consumer(ch)    // bidirectional ch tự động convert thành <-chan
}

Directional channels là một dạng documentation — compiler đảm bảo các functions tôn trọng vai trò của mình.


Part 3: Select Statement

select cho phép goroutine chờ nhiều channel operations cùng lúc — giống như switch nhưng dành cho channels:

package main
 
import (
    "fmt"
    "time"
)
 
func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
 
    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "một"
    }()
 
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "hai"
    }()
 
    // Chờ channel nào gửi trước thì nhận
    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println("Nhận từ ch1:", msg)
        case msg := <-ch2:
            fmt.Println("Nhận từ ch2:", msg)
        }
    }
}

Non-Blocking với Default

select {
case msg := <-ch:
    fmt.Println("Nhận được:", msg)
default:
    fmt.Println("Không có message — tiếp tục thôi")
}

Timeout Pattern

select {
case result := <-ch:
    fmt.Println("Kết quả:", result)
case <-time.After(2 * time.Second):
    fmt.Println("Timeout — bỏ cuộc thôi")
}

Done Channel Pattern (Tín Hiệu Dừng)

func worker(done <-chan struct{}) {
    for {
        select {
        case <-done:
            fmt.Println("Worker đang dừng")
            return
        default:
            // làm việc...
            time.Sleep(100 * time.Millisecond)
        }
    }
}
 
func main() {
    done := make(chan struct{})
    go worker(done)
 
    time.Sleep(500 * time.Millisecond)
    close(done) // báo hiệu tất cả workers dừng lại
}

Part 4: sync Package

sync.Mutex — Mutual Exclusion

Khi các goroutines chia sẻ mutable state (không qua channels), dùng Mutex để tránh data race:

package main
 
import (
    "fmt"
    "sync"
)
 
type SafeCounter struct {
    mu    sync.Mutex
    value int
}
 
func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}
 
func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}
 
func main() {
    counter := &SafeCounter{}
    var wg sync.WaitGroup
 
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }
 
    wg.Wait()
    fmt.Println("Kết quả cuối:", counter.Value()) // luôn là 1000
}

Chạy với go run -race để phát hiện data races trong quá trình development.

sync.RWMutex — Read-Write Lock

Khi có nhiều readers và ít writers, RWMutex cải thiện performance:

type Cache struct {
    mu    sync.RWMutex
    store map[string]string
}
 
func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()         // nhiều reads đồng thời được phép
    defer c.mu.RUnlock()
    v, ok := c.store[key]
    return v, ok
}
 
func (c *Cache) Set(key, value string) {
    c.mu.Lock()         // exclusive write lock
    defer c.mu.Unlock()
    c.store[key] = value
}

sync.Once — Chạy Đúng Một Lần

sync.Once đảm bảo một function chỉ chạy đúng một lần, dù được gọi từ nhiều goroutines — hoàn hảo cho lazy initialization:

package main
 
import (
    "fmt"
    "sync"
)
 
type Database struct {
    connection string
}
 
var (
    db   *Database
    once sync.Once
)
 
func getDB() *Database {
    once.Do(func() {
        fmt.Println("Đang kết nối database...") // chỉ chạy đúng một lần
        db = &Database{connection: "postgres://localhost/mydb"}
    })
    return db
}
 
func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            d := getDB()
            fmt.Println("Lấy được db:", d.connection)
        }()
    }
    wg.Wait()
    // "Đang kết nối database..." chỉ in một lần duy nhất
}

Part 5: Context Package

Package context là cách Go truyền tín hiệu cancel, deadline, và request-scoped values qua call tree. Đây là thứ cần thiết cho HTTP handlers, database calls, và bất cứ thứ gì có timeout.

context.WithTimeout

package main
 
import (
    "context"
    "fmt"
    "time"
)
 
func fetchData(ctx context.Context) (string, error) {
    // Mô phỏng thao tác chậm
    select {
    case <-time.After(3 * time.Second): // mất 3 giây
        return "data", nil
    case <-ctx.Done(): // bị cancel hoặc timeout
        return "", ctx.Err()
    }
}
 
func main() {
    // Cho operation 1 giây để hoàn thành
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel() // luôn gọi cancel để giải phóng resources
 
    result, err := fetchData(ctx)
    if err != nil {
        fmt.Println("Lỗi:", err) // context deadline exceeded
        return
    }
    fmt.Println("Kết quả:", result)
}

context.WithCancel

func main() {
    ctx, cancel := context.WithCancel(context.Background())
 
    // Launch workers biết lắng nghe context
    for i := 0; i < 3; i++ {
        go func(id int) {
            for {
                select {
                case <-ctx.Done():
                    fmt.Printf("Worker %d đang dừng: %v\n", id, ctx.Err())
                    return
                default:
                    fmt.Printf("Worker %d đang làm việc...\n", id)
                    time.Sleep(200 * time.Millisecond)
                }
            }
        }(i)
    }
 
    time.Sleep(500 * time.Millisecond)
    cancel() // cancel tất cả workers
    time.Sleep(100 * time.Millisecond) // chờ goroutines kết thúc
}

context.WithValue

Truyền request-scoped data (user ID, trace ID, auth token) mà không làm rối function signatures:

type contextKey string
 
const userIDKey contextKey = "userID"
 
func withUserID(ctx context.Context, userID string) context.Context {
    return context.WithValue(ctx, userIDKey, userID)
}
 
func getUserID(ctx context.Context) (string, bool) {
    userID, ok := ctx.Value(userIDKey).(string)
    return userID, ok
}
 
// Dùng trong HTTP middleware
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        userID := r.Header.Get("X-User-ID")
        ctx := withUserID(r.Context(), userID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

Quy tắc dùng context:

  1. Truyền ctx làm tham số đầu tiên cho mọi function thực hiện I/O
  2. Không bao giờ lưu context trong struct — truyền tường minh
  3. Luôn defer cancel() sau WithTimeout hoặc WithCancel
  4. Dùng context.Background() ở top level (main, tests)
  5. Dùng context.TODO() làm placeholder khi chưa chắc cần dùng context không

Part 6: Testing với go test

Go có testing package ngay trong standard library — không cần third-party.

Tests Cơ Bản

// calculator.go
package calculator
 
func Add(a, b int) int      { return a + b }
func Subtract(a, b int) int { return a - b }
func Multiply(a, b int) int { return a * b }
func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("chia cho 0")
    }
    return a / b, nil
}
// calculator_test.go
package calculator
 
import (
    "testing"
)
 
func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; muốn 5", result)
    }
}
 
func TestDivide(t *testing.T) {
    _, err := Divide(10, 0)
    if err == nil {
        t.Error("mong đợi lỗi khi chia cho 0, nhưng nhận được nil")
    }
}

Chạy với: go test ./...


Table-Driven Tests

Pattern test idiomatic nhất trong Go — test nhiều cases với một function:

func TestAdd_TableDriven(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"số dương", 2, 3, 5},
        {"số âm", -2, -3, -5},
        {"hỗn hợp", -2, 3, 1},
        {"toàn số 0", 0, 0, 0},
        {"số lớn", 1000000, 2000000, 3000000},
    }
 
    for _, tc := range tests {
        t.Run(tc.name, func(t *testing.T) {
            result := Add(tc.a, tc.b)
            if result != tc.expected {
                t.Errorf("Add(%d, %d) = %d; muốn %d", tc.a, tc.b, result, tc.expected)
            }
        })
    }
}

Chạy một subtest cụ thể: go test -run TestAdd_TableDriven/số_âm


Benchmarks

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(100, 200)
    }
}
 
func BenchmarkMultiply(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Multiply(100, 200)
    }
}

Chạy với: go test -bench=. -benchmem

BenchmarkAdd-8         1000000000    0.23 ns/op    0 B/op    0 allocs/op
BenchmarkMultiply-8    1000000000    0.23 ns/op    0 B/op    0 allocs/op

Testing với Interfaces (Mocking)

Go làm mocking dễ dàng thông qua interfaces — không cần reflection-based mocking framework:

// email.go
type EmailSender interface {
    Send(to, subject, body string) error
}
 
type UserService struct {
    email EmailSender
}
 
func (s *UserService) Register(name, emailAddr string) error {
    // ... tạo user ...
    return s.email.Send(emailAddr, "Chào mừng!", "Cảm ơn bạn đã đăng ký, "+name)
}
// email_test.go
type MockEmailSender struct {
    SentMessages []struct{ To, Subject, Body string }
    ShouldFail   bool
}
 
func (m *MockEmailSender) Send(to, subject, body string) error {
    if m.ShouldFail {
        return fmt.Errorf("dịch vụ email không khả dụng")
    }
    m.SentMessages = append(m.SentMessages, struct{ To, Subject, Body string }{to, subject, body})
    return nil
}
 
func TestUserService_Register(t *testing.T) {
    mock := &MockEmailSender{}
    svc := &UserService{email: mock}
 
    err := svc.Register("Alice", "[email protected]")
    if err != nil {
        t.Fatalf("lỗi không mong đợi: %v", err)
    }
 
    if len(mock.SentMessages) != 1 {
        t.Errorf("mong đợi 1 email được gửi, nhận được %d", len(mock.SentMessages))
    }
    if mock.SentMessages[0].To != "[email protected]" {
        t.Errorf("sai người nhận: %s", mock.SentMessages[0].To)
    }
}

Part 7: HTTP với net/http

Standard library net/http của Go đã sẵn sàng cho production — nhiều công ty chạy Go HTTP servers chỉ với thư viện này (không cần Express.js hay tương tự).

HTTP Server Đơn Giản

package main
 
import (
    "fmt"
    "net/http"
)
 
func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Xin chào, %s!", r.URL.Query().Get("name"))
}
 
func main() {
    http.HandleFunc("/hello", helloHandler)
    fmt.Println("Server khởi động trên :8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

JSON Encoding và Decoding

import (
    "encoding/json"
    "net/http"
)
 
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email,omitempty"` // bỏ qua nếu rỗng
}
 
// Encode struct thành JSON response
func writeJSON(w http.ResponseWriter, status int, data any) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}
 
// Decode JSON request body
func readJSON(r *http.Request, dst any) error {
    return json.NewDecoder(r.Body).Decode(dst)
}

Part 8: Build một REST API

Hãy tổng hợp tất cả lại — một REST API đơn giản quản lý users, chỉ dùng standard library:

package main
 
import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    "strings"
    "sync"
)
 
// Domain types
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}
 
// In-memory store (thread-safe)
type UserStore struct {
    mu     sync.RWMutex
    users  map[int]User
    nextID int
}
 
func NewUserStore() *UserStore {
    return &UserStore{users: make(map[int]User), nextID: 1}
}
 
func (s *UserStore) GetAll() []User {
    s.mu.RLock()
    defer s.mu.RUnlock()
    result := make([]User, 0, len(s.users))
    for _, u := range s.users {
        result = append(result, u)
    }
    return result
}
 
func (s *UserStore) GetByID(id int) (User, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    u, ok := s.users[id]
    return u, ok
}
 
func (s *UserStore) Create(name, email string) User {
    s.mu.Lock()
    defer s.mu.Unlock()
    u := User{ID: s.nextID, Name: name, Email: email}
    s.users[s.nextID] = u
    s.nextID++
    return u
}
 
func (s *UserStore) Delete(id int) bool {
    s.mu.Lock()
    defer s.mu.Unlock()
    if _, ok := s.users[id]; !ok {
        return false
    }
    delete(s.users, id)
    return true
}
 
// HTTP helpers
func writeJSON(w http.ResponseWriter, status int, data any) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}
 
func writeError(w http.ResponseWriter, status int, message string) {
    writeJSON(w, status, map[string]string{"error": message})
}
 
// Handler
type UserHandler struct {
    store *UserStore
}
 
func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // Route: /users hoặc /users/{id}
    path := strings.TrimPrefix(r.URL.Path, "/users")
    path = strings.Trim(path, "/")
 
    if path == "" {
        // /users
        switch r.Method {
        case http.MethodGet:
            writeJSON(w, http.StatusOK, h.store.GetAll())
        case http.MethodPost:
            var body struct {
                Name  string `json:"name"`
                Email string `json:"email"`
            }
            if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
                writeError(w, http.StatusBadRequest, "JSON không hợp lệ")
                return
            }
            if body.Name == "" || body.Email == "" {
                writeError(w, http.StatusBadRequest, "cần có name và email")
                return
            }
            user := h.store.Create(body.Name, body.Email)
            writeJSON(w, http.StatusCreated, user)
        default:
            writeError(w, http.StatusMethodNotAllowed, "method không được phép")
        }
        return
    }
 
    // /users/{id}
    id, err := strconv.Atoi(path)
    if err != nil {
        writeError(w, http.StatusBadRequest, "user ID không hợp lệ")
        return
    }
 
    switch r.Method {
    case http.MethodGet:
        user, ok := h.store.GetByID(id)
        if !ok {
            writeError(w, http.StatusNotFound, "không tìm thấy user")
            return
        }
        writeJSON(w, http.StatusOK, user)
    case http.MethodDelete:
        if !h.store.Delete(id) {
            writeError(w, http.StatusNotFound, "không tìm thấy user")
            return
        }
        w.WriteHeader(http.StatusNoContent)
    default:
        writeError(w, http.StatusMethodNotAllowed, "method không được phép")
    }
}
 
func main() {
    store := NewUserStore()
    handler := &UserHandler{store: store}
 
    mux := http.NewServeMux()
    mux.Handle("/users", handler)
    mux.Handle("/users/", handler)
 
    server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }
 
    fmt.Println("🚀 Server chạy tại http://localhost:8080")
    log.Fatal(server.ListenAndServe())
}

Test thử:

# Tạo users
curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "email": "[email protected]"}'
 
curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Bob", "email": "[email protected]"}'
 
# Lấy tất cả
curl http://localhost:8080/users
 
# Lấy một user
curl http://localhost:8080/users/1
 
# Xóa
curl -X DELETE http://localhost:8080/users/1

Part 9: Concurrency Patterns

Worker Pool

Pattern phổ biến: giới hạn concurrency với N workers xử lý một queue jobs:

package main
 
import (
    "fmt"
    "sync"
)
 
type Job struct {
    ID   int
    Data string
}
 
type Result struct {
    JobID  int
    Output string
}
 
func worker(id int, jobs <-chan Job, results chan<- Result, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        // Xử lý job
        output := fmt.Sprintf("Worker %d xử lý job %d: %s", id, job.ID, job.Data)
        results <- Result{JobID: job.ID, Output: output}
    }
}
 
func main() {
    const numWorkers = 3
    const numJobs = 10
 
    jobs := make(chan Job, numJobs)
    results := make(chan Result, numJobs)
    var wg sync.WaitGroup
 
    // Khởi động worker pool
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }
 
    // Gửi jobs
    for j := 1; j <= numJobs; j++ {
        jobs <- Job{ID: j, Data: fmt.Sprintf("data-%d", j)}
    }
    close(jobs) // hết jobs rồi
 
    // Đóng results khi tất cả workers xong
    go func() {
        wg.Wait()
        close(results)
    }()
 
    // Thu thập kết quả
    for r := range results {
        fmt.Println(r.Output)
    }
}

Fan-Out / Fan-In

// Fan-out: phân phối công việc cho nhiều goroutines
// Fan-in: gộp kết quả lại vào một channel
 
func fanOut(input <-chan int, numWorkers int) []<-chan int {
    outputs := make([]<-chan int, numWorkers)
    for i := 0; i < numWorkers; i++ {
        out := make(chan int)
        outputs[i] = out
        go func(out chan<- int) {
            for v := range input {
                out <- v * v // bình phương mỗi số
            }
            close(out)
        }(out)
    }
    return outputs
}
 
func fanIn(channels ...<-chan int) <-chan int {
    merged := make(chan int)
    var wg sync.WaitGroup
    for _, ch := range channels {
        wg.Add(1)
        go func(c <-chan int) {
            defer wg.Done()
            for v := range c {
                merged <- v
            }
        }(ch)
    }
    go func() {
        wg.Wait()
        close(merged)
    }()
    return merged
}

Pipeline

Kết nối các stages lại, mỗi stage xử lý và chuyển tiếp values:

func generate(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}
 
func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}
 
func filter(in <-chan int, pred func(int) bool) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            if pred(n) {
                out <- n
            }
        }
        close(out)
    }()
    return out
}
 
func main() {
    // Pipeline: generate → square → lọc số chẵn
    nums := generate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    squared := square(nums)
    evens := filter(squared, func(n int) bool { return n%2 == 0 })
 
    for n := range evens {
        fmt.Println(n) // 4, 16, 36, 64, 100
    }
}

Những Cạm Bẫy Thường Gặp

1. Goroutine Leak — Quên Không Dừng Workers

// ❌ Goroutine chạy mãi mãi nếu không ai đọc từ ch
func leakyFunc() {
    ch := make(chan int)
    go func() {
        for {
            ch <- expensiveComputation() // block mãi nếu caller return
        }
    }()
    // caller return mà không đọc ch — goroutine bị leak
}
 
// ✅ Dùng done/ctx channel làm tín hiệu dừng
func safeFunc(ctx context.Context) <-chan int {
    ch := make(chan int)
    go func() {
        defer close(ch)
        for {
            select {
            case <-ctx.Done():
                return
            case ch <- expensiveComputation():
            }
        }
    }()
    return ch
}

2. Đóng Channel Hai Lần (Panic)

// ❌ Đóng channel đã đóng sẽ panic
close(ch)
close(ch) // panic: close of closed channel
 
// ✅ Dùng sync.Once để đóng đúng một lần
var once sync.Once
safeClose := func() {
    once.Do(func() { close(ch) })
}

3. Capture Loop Variable Trong Goroutine

// ❌ Tất cả goroutines thấy cùng một biến (giá trị cuối cùng)
for i := 0; i < 3; i++ {
    go func() {
        fmt.Println(i) // có thể in ra 3 3 3
    }()
}
 
// ✅ Truyền làm argument — tạo một bản sao cho mỗi goroutine
for i := 0; i < 3; i++ {
    go func(i int) {
        fmt.Println(i) // in ra 0 1 2 (thứ tự bất kỳ)
    }(i)
}

4. Không Xử Lý Errors Từ Goroutines

// ❌ Errors bị mất im lìm
go func() {
    if err := doWork(); err != nil {
        // chẳng ai nghe cả!
    }
}()
 
// ✅ Gửi errors qua error channel
errCh := make(chan error, 1)
go func() {
    if err := doWork(); err != nil {
        errCh <- err
    }
    close(errCh)
}()
 
if err := <-errCh; err != nil {
    log.Fatal(err)
}

5. Bỏ Qua Race Detector

Luôn chạy go test -race hoặc go run -race trong quá trình development — race detector tích hợp sẵn của Go phát hiện data races ngay lúc runtime.


Tổng Kết và Những Điều Cần Nhớ

Goroutines & Channels:

✅ go func() launch một goroutine — nhẹ, khởi tạo rẻ
✅ Channels là conduits có type — cách ưu tiên để goroutines chia sẻ dữ liệu
✅ Unbuffered channels đồng bộ; buffered channels tách biệt producer/consumer
✅ select chờ nhiều channels — cho phép timeout, cancellation, non-blocking ops

Sync Primitives:

✅ sync.WaitGroup — chờ một nhóm goroutines hoàn thành
✅ sync.Mutex / sync.RWMutex — bảo vệ shared state khỏi concurrent access
✅ sync.Once — chạy initialization đúng một lần, an toàn

Context:

✅ Truyền ctx context.Context làm tham số đầu tiên cho mọi function thực hiện I/O
✅ Dùng WithTimeout cho deadline, WithCancel để cancel thủ công
✅ Luôn defer cancel() — để leak context gây resource leak

Testing:

✅ Table-driven tests là idiomatic Go — quản lý nhiều cases gọn gàng
✅ Dùng interfaces để mock dependencies — không cần reflection framework
✅ go test -race phát hiện race conditions sớm

HTTP & REST:

✅ net/http đã sẵn sàng cho production — không cần framework với hầu hết use cases
✅ encoding/json với struct tags cho JSON APIs sạch đẹp
✅ Dùng http.ServeMux để routing, custom http.Handler cho code có cấu trúc


Bước Tiếp Theo

Bạn đã hoàn thành Go Learning Roadmap! Đây là hướng để đi sâu hơn:

Các bài liên quan:

📬 Đăng ký nhận bản tin

Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.

💬 Comments

Đăng nhập để bình luận

Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.