avatar
Frances Guerrero

Editor at Eduport

  • March 18, 2024
  • 5 min read
  • 266
  • 2K
March 18, 2024|
Research

Golang: Goroutine Leak là gì? Làm thế nào để tìm và xử lý nó.

Goroutine Leak Detector

Context

Trước khi đọc bài viết này hãy giả sử một tình huống rằng. Hôm nay ngoài trời đang nắng 40 độ C, trước khi đi làm bạn vào tắm cái cho nó mát, xong bật điều hoà và ngồi lướt tóp tóp chờ tóc khô rồi đi làm. Khi đi làm bạn lại quên tắt nước, tắt điều hoà. Và cái kết là thế nào thì bạn hiểu rồi đó, bạn sẽ phải trả giá bằng tiền điện và tiền nước cho khoản rò rỉ đó mặc dù nó không giúp ích được gì cho bạn. Trong phần mềm cũng vậy, dò rỉ bộ nhớ khiến ứng dụng bị hết bộ nhớ và giảm hiệu suất của ứng dụng.

Rò rỉ bộ nhớ có thể là mối lo ngại đáng kể trong bất kỳ ứng dụng phần mềm nào, kể cả những ứng dụng được viết bằng Golang. Rò rỉ bộ nhớ xảy ra khi một chương trình vô tình giữ lại bộ nhớ không còn cần thiết, dẫn đến tài nguyên hệ thống cạn kiệt dần. Theo thời gian, những rò rỉ này có thể làm giảm hiệu suất và thậm chí khiến ứng dụng bị treo. 

Làm thế nào một Goroutine có thể xảy ra Leak?

Goroutine leak được hiểu là khi một  goroutine is được bắt đầu nhưng không bao giờ được hoàn thành. Vì mỗi goroutine chiếm một lượng bộ nhớ hữu hạn nên việc rò rỉ goroutine sẽ dẫn đến rò rỉ bộ nhớ và quá trình hết bộ nhớ.

Rò rỉ bộ nhớ xảy ra khi bộ nhớ được phân bổ không được chương trình giải phóng hoặc giải phóng đúng cách. Trong Golang, trình thu gom rác (GC) quản lý việc cấp phát bộ nhớ và tự động lấy lại bộ nhớ không sử dụng. Tuy nhiên, rò rỉ bộ nhớ vẫn có thể xảy ra do cách sử dụng không chính xác, chu kỳ tham chiếu hoặc tài nguyên bên ngoài không được giải phóng đúng cách.

Vậy nguyên nhân nào để một goroutine không thể kết thúc được:

  1. Một gorountine đang chờ để đọc từ một channel nhưng dữ liệu không bao giờ được gửi đến
  2. Một goroutine  cố gắng ghi vào một channel nhưng lại bị chặn vì dữ liệu hiện có không bao giờ được đọc (buffered channel)

Ví dụ

func search() []string {
   result := make(chan []string)
   for i := 0; i < 3; i++ {
      go func() {
         result <- get()
      }()
   }

   return <-result
}
func get() []string {
   time.Sleep(time.Duration(rand.Intn(10) * int(time.Millisecond)))
   return []string{"hello", "how"}
}

 

Mấu chốt của vấn đề là hàm search chỉ lấy dữ liệu đầu tiên và trả về dữ liệu cho người gọi trong khi con goroutine khác đã bắt đầu hoàn thành nghiêm túc các tìm kiếm của chúng và cố gắng ghi chúng vào channel nhất định

Làm thế nào để có thể phát hiện được rò rỉ bộ nhớ?

Một số package của bên thứ ba được thiết kế đặc biệt để phát hiện rò rỉ bộ nhớ trong ứng dụng Golang có thể tự động hóa quy trình. Một số tùy chọn phổ biến bao gồm

go-torch: This tool profiles memory and CPU usage to help identify bottlenecks and memory leaks.

golangci-lint: It offers a comprehensive set of linters, including memory leak detection, which can be integrated into your CI/CD pipeline.

heapster: This tool tracks memory allocations and provides detailed insights into memory usage patterns.

uber-go/goleak: GitHub - uber-go/goleak: Goroutine leak detector

zimmski/go-leak: GitHub - zimmski/go-leak: Detect all kinds of leaks in Go