GoLang 通道

Golang Series

Hello GoLang: https://blog.yexca.net/archives/154
GoLang (var and const) 变量与常量: https://blog.yexca.net/archives/155
GoLang (func) 函数: https://blog.yexca.net/archives/156
GoLang (slice and map) 切片: https://blog.yexca.net/archives/160
GoLang (OOP) 面向对象: https://blog.yexca.net/archives/162
GoLang (reflect) 反射: https://blog.yexca.net/archives/204
GoLang (struct tag) 结构体标签: https://blog.yexca.net/archives/205
GoLang (goroutine) go 程: https://blog.yexca.net/archives/206
GoLang (channel) 通道: 本文


Go 程 (goroutine) 可以通过 channel 进行传递数据,引用类型 channel 可用于多个 goroutine 通讯,其内部实现了同步,确保并发安全

有点类似 RabbitMQ (仅个人为方便学习所类比,实则为不同东西)

定义变量

channel 为引用类型,复制或函数调用时将引用同一个 channel 对象,零值为 nil

通过 make() 函数创建,例如

c := make(chan int)
// 添加容量为3
c := make(chan int, 3)

当容量为 0 时,channel 是无缓冲阻塞读写的,大于 0 时有缓存是非阻塞的,直至写满才阻塞

通过 <- 来接收和发送数据

// 发送数据到 channel
channel <- 3
// 接收并丢弃
<-channel // 注意无空格
// 接收并赋值给变量
x := <-channel
// 接收并赋值给变量,并判断是否接收成功(channel 是否为空)
data, flag := <-channel

无缓冲

package main

import "fmt"

func main() {
    c := make(chan int)
    go func() {
        defer fmt.Println("A.defer")
        c <- 6
        fmt.Println("A running")
    }()

    num := <-c
    fmt.Println("num =", num)

    fmt.Println("main over")
}

有缓冲

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int, 3)

    go func() {
        defer fmt.Println("A over")
        for i := 0; i < 3; i++ {
            c <- i
            fmt.Println("A goroutine, i =", i, "len =", len(c), "cap =", cap(c))
        }
    }()

    time.Sleep(2 * time.Second)

    for i := 0; i < 3; i++ {
        num := <-c
        fmt.Println("main, num =", num)
    }

    fmt.Println("main over")
}

关闭 channel

通过 close() 关闭

package main

import "fmt"

func main() {
    c := make(chan int, 5)

    go func() {
        for i := 0; i < 5; i++ {
            c <- i
        }
        // 关闭
        close(c)
    }()

    for {
        if data, ok := <-c; ok {
            fmt.Println(data)
        } else {
            break
        }
    }

    fmt.Println("Main Finished")
}

使用 range

上述 main 的 for 循环可以简写使用 range

package main

import "fmt"

func main() {
    c := make(chan int, 5)

    go func() {
        for i := 0; i < 5; i++ {
            c <- i
        }

        close(c)
    }()

    //for {
    //  if data, ok := <-c; ok {
    //      fmt.Println(data)
    //  } else {
    //      break
    //  }
    //}

    for data := range c {
        fmt.Println(data)
    }
    fmt.Println("Main Finished")
}

单向 channel

默认情况下 channel 是双向的,即可读可写,也可以指定通道方向,只读或只写

var c chan int // 声明正常双向 channel
// c1 只可写
var c1 chan<- int
// c2 只可读
var c2 <-chan int

可以把双向 channel 转为单向,反之不可。也就是可以定义函数形参为单向,但传递双向

//   chan<- // 只写
func counter(out chan<- int) {
    defer close(out)
    for i := 0; i < 5; i++ {
        out <- i // 如果对方不读 会阻塞
    }
}
 
//   <-chan // 只读
func printer(in <-chan int) {
    for num := range in {
        fmt.Println(num)
    }
}
 
func main() {
    c := make(chan int) // 双向
 
    go counter(c) // 生产者
    printer(c)    // 消费者
 
    fmt.Println("done")
}

select

select 可以监听多个 channel 上的数据流动,语法与 switch 类似,但每个 case 语句里必须是一个 IO 操作

一般放到 for{} 语句块中

package main

import "fmt"

func main() {
    c := make(chan int)
    quit := make(chan int)

    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }

        quit <- 0
    }()

    // main
    x, y := 1, 1
    for {
        select {
        case c <- x:
            tmp := x
            x = y
            y = tmp + y
        case <-quit:
            fmt.Println("quit")
            return
         // 可以有 default,此例不需要
        }
    }
}
This post is licensed under CC BY-NC-SA 4.0 by the author.