学习GO语言的管线并发模式

93 阅读2分钟

什么是流水线模式?

根据维基百科的说法,流水线

...由一组处理元素组成,每个元素的输出是下一个元素的输入。

在Go中,这相当于实现了接收一个通道并返回另一个通道供另一个函数使用的函数,通常每个函数都会以一种对后续步骤有用的方式来改变结果。

Pipeline

例如,在一个由多个步骤组成的程序中,它们可以以这样的方式实现,即初始步骤的输出是下一个步骤的输入;然而,需要记住的一点是,这并不意味着这些步骤应该是连续的,而只是根据程序需要的实现方式来分支。

当构建管道时,考虑到流程中最慢的步骤将决定一切的快慢;所以在某些情况下,对分支的步骤应用不同的并发模式可能是有意义的,回顾一下,总是有基准。

管道并发模式示例

下面的例子(为简洁起见省略了错误)。

1func main() {
2	recordsC, _ := readCSV("file1.csv")
3
4	for val := range sanitize(titleize(recordsC)) {
5		fmt.Printf("%v\n", val)
6	}
7}

将读取一个CSV文件,并对读取的每个值应用一些逻辑,它包括:

  • L2:我们读取一个CSV文件,结果是一个频道recordsC
  • L4: 我们用recordsC 作为titleize 的输入,并且sanitize

函数titleize

 1func titleize(strC <-chan []string) <-chan []string {
 2	ch := make(chan []string)
 3
 4	go func() {
 5		for val := range strC {
 6			val[0] = strings.Title(val[0])
 7			val[1], val[2] = val[2], val[1]
 8
 9			ch <- val
10		}
11
12		close(ch)
13	}()
14
15	return ch
16}
  • L2:创建ch 通道(它将被用作下一个管道步骤的输入)。
  • L4-13: 使用一个goroutine来。
    • L5:接收来自通道strC 的值。
    • L6-7:通过标题化和切换这些值,对这些值进行转换,并且
    • L9: 将这些变化发送到该通道ch
    • L12: 在for 完成后,通道ch 被关闭,以表示这个管道步骤的结束。
  • L15:返回ch 通道。

从实现上来说,sanitize 这个函数与前面的函数有点类似。

 1func sanitize(strC <-chan []string) <-chan []string {
 2	ch := make(chan []string)
 3
 4	go func() {
 5		for val := range strC {
 6			if len(val[0]) > 3 {
 7				fmt.Println("skipped ", val)
 8				continue
 9			}
10
11			ch <- val
12		}
13
14		close(ch)
15	}()
16
17	return ch
18}

最大的区别是应用于接收值的实际逻辑。

  • L6-9:我们跳过任何长度超过3的值。

如果你记得main ,我们有使用这两个函数的管道调用。

sanitize(titleize(recordsC))

现在,如果把它改成类似的东西。

titleize(sanitize(recordsC))

正如预期的那样,输出将发生变化,因为现在sanitize 发生在titleize 之前;但这些函数在我们的流水线中仍然表现为步骤。

总结

管道是一种众所周知的并发模式,很容易在Go中实现,并允许我们使用语言的原语来描述并发的步骤,然而,在你的程序中实现它之前,确保每个步骤都有基准,这样你就有一个比较你的变化的基线,通常使用goroutines可以提高我们程序的性能,但在某些情况下它可能使事情变得更糟。