介绍
pipeline类似于unix中的管道,特征是将复杂的问题,分解成一个一个的简单的单元,每一个单元的输出结果是下一个单元的输入参数
不好的示例
下面是一个简单的pipeline的示例,他只有简单的两步操作,一个加1,一个乘以2
package main
import "fmt"
import "time"
func main() {
t := time.Now()
for i := 0; i < 5; i++ {
mul(mul(add(i)))
}
fmt.Println(fmt.Sprintf("cost:%s", time.Since(t)))
}
func add(i int) int {
time.Sleep(time.Second)
return i + 1
}
func mul(i int) int {
time.Sleep(time.Second)
return i * 2
}
这个pipeline有个问题是,他只能将每次执行完,才能执行下一个,没有利用好go语言的并发特性
好的示例
package main
import "fmt"
import "sync"
import "time"
func main() {
t := time.Now()
done := make(chan interface{})
go func() {
defer close(done)
time.Sleep(time.Second * 2)
}()
var wg sync.WaitGroup
wg.Add(1)
// 生成channel, 要使用WaitGroup来确保生成完毕
ch := gen(done, &wg)
chRst := mul(done, add(done, ch))
for range chRst {
}
wg.Wait()
fmt.Println(fmt.Sprintf("cost:%s", time.Since(t)))
}
func gen(done <-chan interface{}, wg *sync.WaitGroup) <-chan int {
chRst := make(chan int)
go func() {
defer close(chRst)
defer wg.Done()
for i := 0; i < 5; i++ {
select {
case <-done:
return
case chRst <- i:
}
}
}()
return chRst
}
func mul(done <-chan interface{}, ch <-chan int) <-chan int {
chRst := make(chan int)
go func() {
defer close(chRst)
for {
select {
case <-done:
return
case r, ok := <-ch:
// 不停的计算,知道源头channel已经关闭
if ok == false {
return
}
time.Sleep(time.Second)
chRst <- r * 2
}
}
}()
return chRst
}
func add(done <-chan interface{}, ch <-chan int) <-chan int {
chRst := make(chan int)
go func() {
defer close(chRst)
for {
select {
case <-done:
return
case r, ok := <-ch:
// 不停的计算,知道源头channel已经关闭
if ok == false {
return
}
time.Sleep(time.Second)
chRst <- r + 1
}
}
}()
return chRst
}
这个示例,主要有两步
- 第一步 gen方法返回一个只读channel,异步写入需要计算的数据, 首先保证计算不受源数据的生成影响
- 第二步 mul/add 计算单元返回结果channel, 异步计算后写入channel中,保证每次计算完成后,立马被下一个单元接收;而且单元完全可以根据计算的复杂度继续拆分或者异步执行
这种模式在每个单元耗时比较久的情况下,优势尤其明显