TopDev AI Robot TopDev
#Backend & Microservices#Giải đề & Thuật toán

Concurrency trong Golang: Hiểu sâu về Goroutine leak và cách phòng tránh bằng context.Context

Hoàng Nam (Tech Lead)
📖 2 phút đọc 💬 1 phản hồi
⚡ Tóm tắt 30 giây bởi TopDev AI
AI Overview

Goroutine siêu nhẹ nhưng không phải miễn phí Một trong những tính năng tuyệt vời nhất của Golang là Goroutine: Chỉ tiêu tốn khoảng 2KB bộ nhớ ngăn xếp stack, cho phép bạn tạo hàng trăm ngàn goroutine…

☰ Mục lục bài viết (5 phần) Mở / Đóng mục lục

Goroutine siêu nhẹ nhưng không phải miễn phí#

Một trong những tính năng tuyệt vời nhất của Golang là Goroutine: Chỉ tiêu tốn khoảng 2KB bộ nhớ ngăn xếp (stack), cho phép bạn tạo hàng trăm ngàn goroutine chạy đồng thời mà không làm sập server.

Tuy nhiên, Goroutine không được thu gom rác tự động nếu nó bị treo vĩnh viễn (Blocked). Hiện tượng này gọi là Goroutine Leak (Rò rỉ Goroutine) – nguyên nhân số 1 khiến các dịch vụ viết bằng Go bị phình RAM sau vài tuần chạy production.

Ví dụ kinh điển gây Goroutine Leak#

GO
// LỖI NGUY HIỂM: Goroutine bị treo vô tận khi đọc channel không có buffer
func queryFastestServer() string {
    ch := make(chan string) // Unbuffered channel!

    go func() { ch <- queryServer("server-A") }()
    go func() { ch <- queryServer("server-B") }()

    return <-ch // Nhận kết quả từ server nhanh nhất và thoát hàm
}

Sau khi hàm queryFastestServer() thoát, server chậm hơn vẫn tiếp tục chạy và cố gắng đẩy kết quả vào ch. Nhưng vì không còn ai lắng nghe ch, goroutine thứ hai sẽ bị khóa vĩnh viễn trong bộ nhớ cho tới khi tắt server!

Giải pháp: Sử dụng context.WithTimeout chuẩn mực#

GO
func queryWithTimeout(ctx context.Context) (string, error) {
    ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
    defer cancel()

    ch := make(chan string, 1) // Buffered channel dung lượng 1 để tránh block!

    go func() {
        result := doLongRunningTask()
        select {
        case ch <- result:
        case <-ctx.Done(): // Hủy bỏ nếu đã quá hạn timeout!
            log.Println("Task canceled, exiting goroutine cleanly")
            return
        }
    }()

    select {
    case res := <-ch:
        return res, nil
    case <-ctx.Done():
        return "", ctx.Err() // Trả về context deadline exceeded
    }
}

2 Quy tắc sống còn khi viết Goroutine#

  1. Luôn biết chính xác khi nào và làm sao một goroutine sẽ kết thúc: Đừng bao giờ khởi chạy một goroutine mà không có lối thoát an toàn.
  2. Luôn truyền context.Context làm tham số đầu tiên: Cho phép hủy bỏ tác vụ dây chuyền từ tầng HTTP request xuống tới tầng Database query.

Góc thảo luận#

Anh em dùng công cụ gì để phát hiện Goroutine Leak? Đã từng dùng runtime.NumGoroutine() để đặt alert trên Grafana chưa?


Xem thêm nội dung liên quan:

Chia sẻ thảo luận này:

Thảo luận

1 phản hồi

Ý kiến từ cộng đồng kỹ sư TopDev
💬

Tham gia thảo luận

Cộng đồng mở · Đăng nhập 1-chạm qua Google
  1. Thu Trang (Backend Engineer)
    #1

    Dùng pprof kết hợp với đồ thị Goroutine count trên Grafana là chuẩn nhất! Cứ thấy số lượng Goroutine tăng dần đều theo thời gian mà không giảm là biết ngay có leak.

Bài liên quan trong #Backend & Microservices

1
TopDev AI Companion
AI
✓ Đã sao chép link