说,我们有三种方法来实现“扇入”行为
func MakeChannel(tries int) chan int {
ch := make(chan int)
go func() {
for i := 0; i < tries; i++ {
ch <- i
}
close(ch)
}()
return ch
}
func MergeByReflection(channels ...chan int) chan int {
length := len(channels)
out := make(chan int)
cases := make([]reflect.SelectCase, length)
for i, ch := range channels {
cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch)}
}
go func() {
for length > 0 {
i, line, opened := reflect.Select(cases)
if !opened {
cases[i].Chan = reflect.ValueOf(nil)
length -= 1
} else {
out <- int(line.Int())
}
}
close(out)
}()
return out
}
func MergeByCode(channels ...chan int) chan int {
length := len(channels)
out := make(chan int)
go func() {
var i int
var ok bool
for length > 0 {
select {
case i, ok = <-channels[0]:
out <- i
if !ok {
channels[0] = nil
length -= 1
}
case i, ok = <-channels[1]:
out <- i
if !ok {
channels[1] = nil
length -= 1
}
case i, ok = <-channels[2]:
out <- i
if !ok {
channels[2] = nil
length -= 1
}
case i, ok = <-channels[3]:
out <- i
if !ok {
channels[3] = nil
length -= 1
}
case i, ok = <-channels[4]:
out <- i
if !ok {
channels[4] = nil
length -= 1
}
}
}
close(out)
}()
return out
}
func MergeByGoRoutines(channels ...chan int) chan int {
var group sync.WaitGroup
out := make(chan int)
for _, ch := range channels {
go func(ch chan int) {
for i := range ch {
out <- i
}
group.Done()
}(ch)
}
group.Add(len(channels))
go func() {
group.Wait()
close(out)
}()
return out
}
type MergeFn func(...chan int) chan int
func main() {
length := 5
tries := 1000000
channels := make([]chan int, length)
fns := []MergeFn{MergeByReflection, MergeByCode, MergeByGoRoutines}
for _, fn := range fns {
sum := 0
t := time.Now()
for i := 0; i < length; i++ {
channels[i] = MakeChannel(tries)
}
for i := range fn(channels...) {
sum += i
}
fmt.Println(time.Since(t))
fmt.Println(sum)
}
}
结果是(在1 CPU,我使用了runtime.GOMAXPROCS(1)):
19.869s(MergeByReflection)
2499997500000个
8.483s(MergeByCode)
2499997500000个
4.977s(MergeByGoRoutines)
2499997500000
结果是(在2 CPU,我使用了runtime.GOMAXPROCS(2)):
的 44.94s
2499997500000个
的 10.853s
2499997500000个
的 3.728s
2499997500000
答案 0 :(得分:3)
这是一个初步评论。示例中的通道都是无缓冲的,这意味着它们可能会在放置或获取时间时阻塞。
在此示例中,除了频道管理之外几乎没有其他处理。因此,性能由同步原语支配。实际上,这些代码很少可以并行化。
在MergeByReflection和MergeByCode函数中,select用于侦听多个输入通道,但没有采取任何措施来考虑输出通道(因此可能阻塞,而某些事件可能在其中一个输入通道上可用) 。
在MergeByGoRoutines函数中,不会发生这种情况:当输出通道阻塞时,它不会阻止另一个输入通道被另一个goroutine读取。因此,运行时有更好的机会来并行化goroutine,并减少对输入通道的争用。
MergeByReflection代码是最慢的,因为它有反射的开销,几乎没有任何东西可以并行化。
MergeByGoRoutines函数是最快的,因为它减少了争用(需要更少的同步),并且因为输出争用对输入性能的影响较小。因此,当使用多个内核运行时,它可以带来很小的改进(与其他两种方法相反)。
MergeByReflection和MergeByCode有很多同步活动,在多个核心上运行会对性能产生负面影响。但是,通过使用缓冲通道可以获得不同的性能。