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) 通道: This Page


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.