Önceki cevaplarla ilgili bazı yorumları genişletmek ve daha net bir karşılaştırma sağlamak için, burada şimdiye kadar aynı girdiye verilen her iki yaklaşımın bir örneği, okunacak bir kanal dilimi ve her bir değer için çağrılacak bir fonksiyonun hangisi olduğunu da bilmek gerekir. değerin geldiği kanal.
Yaklaşımlar arasında üç ana fark vardır:
Karmaşıklık. Kısmen bir okuyucu tercihi olsa da, kanal yaklaşımını daha deyimsel, açık ve okunaklı buluyorum.
Verim. Xeon amd64 sistemimde gorutinler + kanallar yansıtma çözümünü yaklaşık iki büyüklük düzeyinde gerçekleştirir (genel olarak Go'daki yansıma genellikle daha yavaştır ve yalnızca kesinlikle gerekli olduğunda kullanılmalıdır). Elbette, sonuçları işleyen fonksiyonda veya değerlerin giriş kanallarına yazılmasında önemli bir gecikme olursa, bu performans farkı kolayca önemsiz hale gelebilir.
Anlambelleği engelleme / arabelleğe alma. Bunun önemi, kullanım durumuna bağlıdır. Çoğu zaman ya önemli olmaz ya da gorutin birleştirme çözümündeki fazladan hafif bir tamponlama, iş hacmi için yardımcı olabilir. Bununla birlikte, yalnızca tek bir yazarın engelinin kaldırılması ve değerinin başka herhangi bir yazarın engeli kaldırılmadan önce tam olarak ele alınması anlamsallığına sahip olmak isteniyorsa, bu yalnızca yansıtma çözümü ile elde edilebilir.
Gönderen kanalın "kimliği" gerekmiyorsa veya kaynak kanalları hiçbir zaman kapatılmayacaksa, her iki yaklaşımın da basitleştirilebileceğini unutmayın.
Goroutine birleştirme kanalı:
// 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)
}
}
Yansıma seçimi:
// 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())
}
}
[ Go oyun alanında tam kod .]