go channel管道的基本使用

Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。

Channel类型

和 map 类似,channel 也一个对应 make 创建的底层数据结构的引用。当我们复制一个 channel 或用于函数参数传递时,我们只是拷贝了一个 channel 引用,因此调用者和被调用者将引用同一个 channel 对象。和其它的引用类型一样,channel 的零值也是 nil。

Channel类型的定义格式如下

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

它包括三种类型的定义。可选的<-代表channel的方向。如果没有指定方向,那么Channel就是双向的,既可以接收数据,也可以发送数据。

chan T          // 可以接收和发送类型为 T 的数据
chan<- float64  // 只可以用来发送 float64 类型的数据
<-chan int      // 只可以用来接收 int 类型的数据

定义一个 channel 时,也需要定义发送到 channel 的值的类型。channel 可以使用内置的 make () 函数来创建:

chan 是创建 channel 所需使用的关键字。Type 代表指定 channel 收发数据的类型。

make(chan Type)  //等价于make(chan Type, 0)
make(chan Type, capacity)

无缓冲的 channel

无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何数据值的通道。
这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。否则,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。

无缓冲的 channel 创建容量为0的就可以了

make(chan Type)
make(chan Type, 0)

如:

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int, 0) //创建无缓冲的通道 c

    go func() {
        defer fmt.Println("子协程结束")

        for i := 0; i < 3; i++ {
            fmt.Println("子协程发送数据: ", i)
            c <- i
        }
    }()

    time.Sleep(2 * time.Second) //延时2s

    for i := 0; i < 3; i++ {
        num := <-c //从c中接收数据,并赋值给num
        fmt.Println("main接收数据: ", num)
    }

    fmt.Println("main结束")

    // 延迟退出,等待子协程结束
    time.Sleep(2 * time.Second)
}

输出结果:

子协程发送数据:  0
main接收数据:  0
子协程发送数据:  1
子协程发送数据:  2
main接收数据:  1
main接收数据:  2
main结束
子协程结束

有缓冲的 channel

有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个数据值的通道。这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也不同。

  • 只有通道中没有要接收的值时,接收动作才会阻塞。
  • 只有通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。

这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的通道没有这种保证。

有缓冲的 channel 创建需要指定容量大小,写入数据超出大小时会写阻塞

make(chan  Type,  capacity)
package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int, 3) //创建无缓冲的通道 c

    go func() {
        defer fmt.Println("子协程结束")

        for i := 0; i < 3; i++ {
            fmt.Println("子协程发送数据: ", i)
            c <- i
        }
    }()

    time.Sleep(2 * time.Second) //延时2s

    for i := 0; i < 3; i++ {
        num := <-c //从c中接收数据,并赋值给num
        fmt.Println("main接收数据: ", num)
    }

    fmt.Println("main结束")

    // 延迟退出,等待子协程结束
    time.Sleep(2 * time.Second)
}

输出结果:

子协程发送数据:  0
子协程发送数据:  1
子协程发送数据:  2
子协程结束
main接收数据:  0
main接收数据:  1
main接收数据:  2
main结束

关闭 channel

如果发送者知道,没有更多的值需要发送到 channel 的话,那么让接收者也能及时知道没有多余的值可接收将是有用的,因为接收者可以停止不必要的接收等待。这可以通过内置的 close 函数来关闭 channel 实现。

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int, 3) //创建无缓冲的通道 c

    go func() {
        defer fmt.Println("子协程结束")

        for i := 0; i < 3; i++ {
            fmt.Println("子协程发送数据: ", i)
            c <- i
        }
        close(c)
    }()

    time.Sleep(2 * time.Second) //延时2s

    for num := range c {
        fmt.Println("main接收数据: ", num)
    }
    fmt.Println("main结束")

    // 延迟退出,等待子协程结束
    time.Sleep(2 * time.Second)
}

输出结果

子协程发送数据:  0
子协程发送数据:  1
子协程发送数据:  2
子协程结束
main接收数据:  0
main接收数据:  1
main接收数据:  2
main结束
  • channel 不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束 range 循环之类的,才去关闭 channel;
  • 关闭 channel 后,无法向 channel 再发送数据 (引发 panic 错误后导致接收立即返回零值);
  • 关闭 channel 后,可以继续从 channel 接收数据;
  • 对于 nil channel,无论收发都会被阻塞。

本文参考:https://learnku.com/docs/bettercoding/1.0/channel-basic-use/6828

0%