Để mở rộng một số nhận xét về các câu trả lời trước đó và để cung cấp một so sánh rõ ràng hơn, đây là một ví dụ về cả hai cách tiếp cận được trình bày cho đến nay với cùng một đầu vào, một phần kênh để đọc và một hàm để gọi cho từng giá trị cũng cần biết kênh giá trị đến từ.
Có ba điểm khác biệt chính giữa các cách tiếp cận:
Tính phức tạp. Mặc dù nó có thể một phần là sở thích của người đọc, tôi thấy cách tiếp cận kênh mang tính thành ngữ, dễ hiểu và dễ đọc hơn.
Hiệu suất. Trên hệ thống Xeon amd64 của tôi, goroutines + channel out thực hiện giải pháp phản xạ khoảng hai bậc độ lớn (nói chung phản xạ trong cờ vây thường chậm hơn và chỉ nên được sử dụng khi thực sự cần thiết). Tất nhiên, nếu có bất kỳ độ trễ đáng kể nào trong việc chức năng xử lý kết quả hoặc trong việc ghi giá trị vào các kênh đầu vào, sự khác biệt về hiệu suất có thể dễ dàng trở nên không đáng kể.
Chặn / đệm ngữ nghĩa. Tầm quan trọng của điều này phụ thuộc vào trường hợp sử dụng. Thông thường, nó sẽ không thành vấn đề hoặc việc đệm thêm một chút trong giải pháp hợp nhất theo quy trình có thể hữu ích cho thông lượng. Tuy nhiên, nếu bạn muốn có ngữ nghĩa mà chỉ một người viết duy nhất được bỏ chặn và giá trị của nó được xử lý hoàn toàn trước khi bất kỳ người viết nào khác được bỏ chặn, thì điều đó chỉ có thể đạt được với giải pháp phản ánh.
Lưu ý, có thể đơn giản hóa cả hai cách tiếp cận nếu "id" của kênh gửi không bắt buộc hoặc nếu các kênh nguồn sẽ không bao giờ bị đóng.
Kênh hợp nhất theo quy trình:
// Process1 calls `fn` for each value received from any of the `chans`
// channels. The arguments to `fn` are the index of the channel the
// value came from and the string value. Process1 returns once all the
// channels are closed.
func Process1(chans []<-chan string, fn func(int, string)) {
// Setup
type item struct {
int // index of which channel this came from
string // the actual string item
}
merged := make(chan item)
var wg sync.WaitGroup
wg.Add(len(chans))
for i, c := range chans {
go func(i int, c <-chan string) {
// Reads and buffers a single item from `c` before
// we even know if we can write to `merged`.
//
// Go doesn't provide a way to do something like:
// merged <- (<-c)
// atomically, where we delay the read from `c`
// until we can write to `merged`. The read from
// `c` will always happen first (blocking as
// required) and then we block on `merged` (with
// either the above or the below syntax making
// no difference).
for s := range c {
merged <- item{i, s}
}
// If/when this input channel is closed we just stop
// writing to the merged channel and via the WaitGroup
// let it be known there is one fewer channel active.
wg.Done()
}(i, c)
}
// One extra goroutine to watch for all the merging goroutines to
// be finished and then close the merged channel.
go func() {
wg.Wait()
close(merged)
}()
// "select-like" loop
for i := range merged {
// Process each value
fn(i.int, i.string)
}
}
Lựa chọn phản chiếu:
// Process2 is identical to Process1 except that it uses the reflect
// package to select and read from the input channels which guarantees
// there is only one value "in-flight" (i.e. when `fn` is called only
// a single send on a single channel will have succeeded, the rest will
// be blocked). It is approximately two orders of magnitude slower than
// Process1 (which is still insignificant if their is a significant
// delay between incoming values or if `fn` runs for a significant
// time).
func Process2(chans []<-chan string, fn func(int, string)) {
// Setup
cases := make([]reflect.SelectCase, len(chans))
// `ids` maps the index within cases to the original `chans` index.
ids := make([]int, len(chans))
for i, c := range chans {
cases[i] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(c),
}
ids[i] = i
}
// Select loop
for len(cases) > 0 {
// A difference here from the merging goroutines is
// that `v` is the only value "in-flight" that any of
// the workers have sent. All other workers are blocked
// trying to send the single value they have calculated
// where-as the goroutine version reads/buffers a single
// extra value from each worker.
i, v, ok := reflect.Select(cases)
if !ok {
// Channel cases[i] has been closed, remove it
// from our slice of cases and update our ids
// mapping as well.
cases = append(cases[:i], cases[i+1:]...)
ids = append(ids[:i], ids[i+1:]...)
continue
}
// Process each value
fn(ids[i], v.String())
}
}
[Toàn mã trên sân chơi cờ vây .]