Pour développer certains commentaires sur les réponses précédentes et fournir une comparaison plus claire, voici un exemple des deux approches présentées jusqu'à présent avec la même entrée, une tranche de canaux à lire et une fonction à appeler pour chaque valeur qui doit également savoir laquelle canal d'où provient la valeur.
Il existe trois différences principales entre les approches:
Complexité. Bien que cela puisse être en partie une préférence du lecteur, je trouve l'approche du canal plus idiomatique, simple et lisible.
Performance. Sur mon système Xeon amd64, les canaux goroutines + exécutent la solution de réflexion d'environ deux ordres de grandeur (en général, la réflexion dans Go est souvent plus lente et ne doit être utilisée que lorsque cela est absolument nécessaire). Bien entendu, s'il y a un retard significatif dans la fonction traitant les résultats ou dans l'écriture des valeurs sur les canaux d'entrée, cette différence de performance peut facilement devenir insignifiante.
Sémantique de blocage / mise en mémoire tampon. L'importance de cela dépend du cas d'utilisation. Le plus souvent, cela n'a pas d'importance ou la légère mise en mémoire tampon supplémentaire dans la solution de fusion de goroutine peut être utile pour le débit. Cependant, s'il est souhaitable d'avoir la sémantique qu'un seul écrivain est débloqué et que sa valeur est entièrement gérée avant qu'un autre écrivain ne soit débloqué, cela ne peut être réalisé qu'avec la solution de réflexion.
Notez que les deux approches peuvent être simplifiées si "l'identifiant" du canal d'envoi n'est pas requis ou si les canaux source ne seront jamais fermés.
Canal de fusion Goroutine:
// 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)
}
}
Réflexion sélectionnez:
// 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())
}
}
[Code complet sur le terrain de jeu Go .]