Các nhà phát triển Go từ lâu đã dựa vào WaitGroups từ gói sync để quản lý các hoạt động đồng thời, nhưng cách tiếp cận truyền thống đòi hỏi việc theo dõi thủ công cẩn thận có thể dẫn đến deadlock hoặc panic. Với việc phát hành Go 1.25 , một phương thức WaitGroup.Go() mới hứa hẹn sẽ loại bỏ những cạm bẫy phổ biến này và làm cho lập trình đồng thời trở nên dễ tiếp cận hơn.
Giải Quyết Những Điểm Đau Lâu Đời
Mô hình WaitGroup truyền thống yêu cầu các nhà phát triển phải gọi thủ công Add() trước khi khởi chạy goroutine và Done() trong mỗi goroutine khi hoàn thành. Cách tiếp cận này, mặc dù có thể hoạt động, đã tạo ra cơ hội cho các lỗi. Thiếu một lần gọi Done() sẽ gây ra deadlock trong Wait(), trong khi gọi Done() nhiều lần hơn Add() sẽ kích hoạt panic với lỗi bộ đếm âm.
Cộng đồng đã nhận thức rõ về những vấn đề này. Nhiều nhà phát triển đã tạo ra các kiểu wrapper hoặc sử dụng các giải pháp của bên thứ ba như golang.org/x/sync/errgroup để đạt được cú pháp sạch hơn. Việc giới thiệu WaitGroup.Go() giải quyết những mối quan tâm này bằng cách tự động xử lý các hoạt động tăng và giảm.
Mô hình WaitGroup truyền thống (Trước phiên bản 1.25)
wg := sync.WaitGroup{}
wg.Add(1)
go doSomethingTheOldWay(&wg, r)
wg.Wait()
func doSomethingTheOldWay(wg *sync.WaitGroup, sleep int) {
defer wg.Done()
time.Sleep(time.Duration(sleep)* time.Second)
}
Cách Tiếp Cận Đơn Giản Mới
Phương thức WaitGroup.Go() chấp nhận một hàm làm đối số và tự động quản lý các hoạt động bộ đếm. Thay vì gọi thủ công Add(1) và đảm bảo Done() được gọi, các nhà phát triển giờ đây có thể đơn giản chuyển hàm của họ cho Go(). Điều này loại bỏ hoàn toàn khả năng các hoạt động bộ đếm không khớp.
Tuy nhiên, phương thức mới này có đưa ra một ràng buộc: vì nó chấp nhận một func() không có tham số, các nhà phát triển phải bao bọc các hàm yêu cầu đối số bên trong closure. Điều này đại diện cho sự đánh đổi giữa an toàn và tính linh hoạt mà nhóm Go đã chọn thực hiện.
Mẫu WaitGroup Mới ( Go 1.25+ )
wg := sync.WaitGroup{}
wg.Go(func() { doSomethingTheNewWay(r) })
wg.Wait()
func doSomethingTheNewWay(sleep int) {
time.Sleep(time.Duration(sleep) * time.Second)
}
Phản Ứng Của Cộng Đồng Và Các Lựa Chọn Thay Thế
Cộng đồng nhà phát triển đã thể hiện sự nhiệt tình với thay đổi này, mặc dù một số bày tỏ thất vọng rằng nó sẽ không được backport cho các phiên bản Go cũ hơn. Chính sách tương thích ngược mạnh mẽ của nhóm Go có nghĩa là hầu hết các đội có thể nâng cấp an toàn, nhưng những ai bị mắc kẹt ở các phiên bản cũ hơn phải dựa vào các giải pháp thay thế.
Bạn chỉ cần sử dụng golang.org/x/sync/errgroup thay thế, cái mà luôn cung cấp kiểu sử dụng này.
Nhiều nhà phát triển đã chỉ ra rằng chức năng tương tự đã có sẵn thông qua errgroup, cái mà cung cấp các tính năng bổ sung như lan truyền lỗi và hủy context. Tuy nhiên, hành vi mặc định của errgroup là hủy tất cả các hoạt động khi một hoạt động thất bại không phải lúc nào cũng mong muốn, làm cho cách tiếp cận WaitGroup đơn giản hơn phù hợp hơn cho một số trường hợp sử dụng nhất định.
Custom WaitGroup Wrapper cho các phiên bản cũ hơn
type WaitGroup struct {
sync.WaitGroup
}
func (wg *WaitGroup) Go(fn func()) {
wg.Add(1)
go func() {
defer wg.Done()
fn()
}()
}
So Sánh Các Mô Hình Đồng Thời
Cộng đồng Go tiếp tục tranh luận về những ưu điểm của các mô hình đồng thời khác nhau. Trong khi WaitGroup cung cấp đồng bộ hóa sạch sẽ, một số nhà phát triển thích sử dụng channel để xử lý lỗi, tạo ra các channel lỗi có buffer để thu thập kết quả từ nhiều goroutine. Mỗi cách tiếp cận có vị trí của nó tùy thuộc vào việc bạn cần lan truyền lỗi, thu thập kết quả, hay đồng bộ hóa đơn giản.
Việc bổ sung WaitGroup.Go() không thay thế những mô hình khác này mà cung cấp một mặc định an toàn hơn cho trường hợp phổ biến của việc chờ đợi nhiều hoạt động hoàn thành mà không cần xử lý lỗi phức tạp hoặc tổng hợp kết quả.
Cải tiến của Go 1.25 cho WaitGroup đại diện cho một sự phát triển chu đáo của các nguyên thủy đồng thời của ngôn ngữ. Bằng cách loại bỏ các nguồn lỗi phổ biến trong khi duy trì sự đơn giản làm cho Go hấp dẫn, thay đổi này sẽ làm cho lập trình đồng thời dễ tiếp cận hơn cho các nhà phát triển ở mọi cấp độ kỹ năng.
Tham khảo: Waitgroups: what they are, how to use them and what changed with Go 1.25
