หากต้องการขยายความคิดเห็นบางส่วนเกี่ยวกับคำตอบก่อนหน้านี้และเพื่อให้การเปรียบเทียบที่ชัดเจนขึ้นนี่คือตัวอย่างของทั้งสองแนวทางที่นำเสนอจนถึงตอนนี้โดยให้ข้อมูลเดียวกันช่องที่จะอ่านและฟังก์ชันในการเรียกค่าแต่ละค่าซึ่งจำเป็นต้องทราบด้วย ช่องค่ามาจาก
มีความแตกต่างหลักสามประการระหว่างแนวทาง:
ความซับซ้อน แม้ว่าบางส่วนอาจเป็นความชอบของผู้อ่าน แต่ฉันพบว่าแนวทางของช่องมีสำนวนตรงไปตรงมาและอ่านได้ง่ายกว่า
ประสิทธิภาพ. ในระบบ Xeon amd64 ของฉัน goroutines + channels out จะดำเนินการแก้ปัญหาการสะท้อนโดยประมาณสองลำดับของขนาด (โดยทั่วไปการสะท้อนใน Go มักจะช้ากว่าและควรใช้เมื่อจำเป็นจริงๆเท่านั้น) แน่นอนว่าหากมีความล่าช้าอย่างมีนัยสำคัญในฟังก์ชั่นการประมวลผลผลลัพธ์หรือในการเขียนค่าลงในช่องอินพุตความแตกต่างของประสิทธิภาพนี้อาจไม่สำคัญได้อย่างง่ายดาย
การปิดกั้น / การบัฟเฟอร์ความหมาย ความสำคัญของสิ่งนี้ขึ้นอยู่กับกรณีการใช้งาน ส่วนใหญ่มักจะไม่สำคัญหรือการบัฟเฟอร์เพิ่มเติมเล็กน้อยในโซลูชันการผสาน goroutine อาจเป็นประโยชน์สำหรับปริมาณงาน อย่างไรก็ตามหากเป็นที่พึงปรารถนาที่จะมีความหมายที่มีเพียงผู้เขียนคนเดียวเท่านั้นที่ถูกยกเลิกการปิดกั้นและค่าของมันได้รับการจัดการอย่างเต็มที่ก่อนที่นักเขียนคนอื่นจะถูกยกเลิกการปิดกั้นสิ่งนั้นสามารถทำได้ด้วยโซลูชันการสะท้อนเท่านั้น
หมายเหตุทั้งสองวิธีสามารถทำให้ง่ายขึ้นได้หากไม่จำเป็นต้องใช้ "id" ของช่องทางการส่งหรือถ้าช่องต้นทางจะไม่ถูกปิด
ช่องรวม 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)
}
}
เลือกการสะท้อน:
// 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 playground ]