Vấn đề của pipeline là hạ nguồn có thể dừng sớm (đủ dữ liệu, người dùng hủy request, lỗi), còn thượng nguồn thì vẫn đang chặn ở lệnh gửi.
Muốn dừng sạch, mọi thao tác gửi phải đi kèm một nhánh thoát.
func gen(ctx context.Context, nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
select {
case out <- n:
case <-ctx.Done(): // downstream gave up: exit instead of blocking
return
}
}
}()
return out
}done channel là cơ chế thô: một chan struct{}, ai cũng chỉ đọc, và close(done) phát tín hiệu broadcast tới mọi bên đang đọc cùng lúc. Đơn giản, không phụ thuộc package nào.
context.Context là done channel cộng thêm ba thứ: deadline (tự hủy khi hết giờ), Err() cho biết lý do (context.Canceled hay DeadlineExceeded), và khả năng lan truyền qua biên API — net/http, database/sql, gRPC đều nhận ctx nên hủy được lan xuống tận query đang chạy.
Thực tế trong service: dùng context ở mọi hàm có I/O (quy ước ctx là tham số đầu tiên); done channel chỉ còn hợp lý cho helper nội bộ nhỏ, không đi qua biên package nào.