065-pipeline

一个程序(函数)只干一件事,将不同的程序通过管道串联起来,完成一件复杂的事情。这是 pipeline 的核心思想。

1. 介绍

平时工作中,可能你会经常遇到这样的例子:

$ cat abc.log | grep "INFO" | awk '{print $5}'

abc.log 文件内容如下

// abc.log
[2018-06-05 21:30:22] [INFO] - id:4568 request:checkupdate response:version 2.2
[2018-06-06 11:30:52] [ERROR] - id:3322 request error
[2018-06-06 11:50:02] [ERROR] - id:9986 request error
[2018-06-07 01:40:13] [INFO] - id:1876 request:download response:version 2.2
[2018-06-07 02:30:44] [INFO] - id:5532 request:downlaod response:version 2.2

上面这一段连续的的命令,意思是将 abc.log 文件里带有 INFO 的行的第 5 列打印到屏幕。这样就把所有 INFO 行的 id 号打印出来了。

这种管线化思想在 golang 里也有体现。

2. golang 里的 pipeline

这里我们使用 Go 语言编程里的一个例子来说明。

Created with Raphaël 2.1.2 开始 Counter Squarer Printer 结束

Counter 是一个自然数生成器,从 0 开始,每次产生一个自然数。Squarer 对接收到的每个自然数做平方计算,然后将计算的结果丢给 Printer 打印到屏幕。

下面是实现代码:

package main

import "fmt"

func main() {
    naturals := make(chan int)
    squares := make(chan int)

    // Counter
    go func() {
        for x := 0; ; x++ {
            naturals <- x
        }
    }()

    // Squarer
    go func() {
        for {
            x := <-naturals
            squares <- x * x
        }
    }()

    for {
        x := <-squares
        fmt.Println(x)
    }
}

不过我们的程序似乎不能结束!

3. 关闭 channel

3.1 close channel

假设我们只希望发送 0 到 99,然后就停止发送。你可能会说,这还不简单,把 Counter 改成下面这样:

go func() {
    for x := 0; x < 100; x++ {
        naturals <- x
    }
}()

如果你真的这样写,你的程序在输出 100 个数的平方后,会报错:

扫描二维码关注公众号,回复: 1940518 查看本文章


这里写图片描述
图1 程序死锁

死锁的原因很简单,Counter 没有数据发送,导致 Squarer 卡在了从 naturals 接收数据上,间接导致 main 函数所在的主协程卡在从 squares 接收数据上。程序彻底死锁。

不过 Golang 比较厉害,能检测出这种情况。

解决死锁的办法是,关闭 channel. 你可以使用 close(natural) 来关闭. Counter 改为下面这样:

go func() {
    for x := 0; x < 100; x++ {
        naturals <- x
    }
    close(naturals)
}()

如果你运行这个程序,没有死锁,但是它也不会结束,而是永无何止的在屏幕上打印 0.

在 Golang 里,从一个已经关闭的 channel 读取数据,会读取到该 channel 对应数据类型的零值。如果向一个已经关闭的 channel 写数据,会引发 panic.

3.2 判断 channel 是否关闭

那如何判断 channel 是否关闭呢?难道检测是不是读取到零值?这显然不行,万一人家发的就是 0 呢?此时我们需要使用 channel 的第二种书写形式:

x, ok := <- naturals

如果 naturals 已经关闭,ok 就是 false. 我们再修改一版:

package main

import "fmt"

func main() {
    naturals := make(chan int)
    squares := make(chan int)

    // Counter
    go func() {
        for x := 0; x < 100; x++ {
            naturals <- x
        }
        close(naturals)
    }()

    // Squarer
    go func() {
        for {
            x, ok := <-naturals
            // 如果 ok 为 false,就关闭 squares 并退出循环
            if !ok {
                close(squares)
                break
            }
            squares <- x * x
        }
    }()

    for {
        x, ok := <-squares
        if !ok {
            break
        }
        fmt.Println(x)
    }
}

3.3 for range 迭代 channel

如果每次都使用 x, ok := <-naturals 这种语法来判断 channel 关闭,感觉有点麻烦,Golang 提供了更加简洁的语法,来简化这种操作:

// Counter
go func() {
    for x := range naturals {
        squares <- x * x
    }
    close(squares)
}

如果 naturals 被关闭,for 循环会自动退出。我们再修改一版:

package main

import "fmt"

func main() {
    naturals := make(chan int)
    squares := make(chan int)

    // Counter
    go func() {
        for x := 0; x < 100; x++ {
            naturals <- x
        }
        close(naturals)
    }()

    // Squarer
    go func() {
        for x := range naturals {
            squares <- x * x
        }
        close(squares)
    }()

    for x := range squares {
        fmt.Println(x)
    }
}

4. 总结

  • 掌握 pipeline 思想
  • 掌握如何关闭 channel
  • 掌握如何判断 channel 是否关闭
  • 掌握 for range 迭代 channel 语法

猜你喜欢

转载自blog.csdn.net/q1007729991/article/details/80788376