文章目录
概念
channel是goroutine之间的通信机制。每个channel都有一个数据类型,指定发送该类型的数据;同时同一时刻只有一个goroutine可以访问channel进行数据发送或访问;channel遵循FIFO原则。
channel的数据传输有以下特点:
- channel的发送方和接收方是不同的goroutine(因为数据的发送和接收行为都会阻塞);
- channel一次只能接收一个元素。
参考
demo
向channel发送一个数据并接收
package main
import (
"fmt"
)
func main() {
// 构建一个通道
ch := make(chan int)
// 开启一个并发匿名函数
go func() {
fmt.Println("start goroutine")
// 通过通道通知main的goroutine
ch <- 0
fmt.Println("exit goroutine")
}()
fmt.Println("wait goroutine")
// 等待匿名goroutine
<-ch
fmt.Println("all done")
}
执行代码,输出如下:
wait goroutine
start goroutine
exit goroutine
all done
代码说明如下:
第 10 行,构建一个同步用的通道。
第 13 行,开启一个匿名函数的并发。
第 18 行,匿名 goroutine 即将结束时,通过通道通知 main 的 goroutine,这一句会一直阻塞直到 main 的 goroutine 接收为止。
第 27 行,开启 goroutine 后,马上通过通道等待匿名 goroutine 结束。
向channel连续发送数据并接收
package main
import (
"fmt"
"time"
)
func main() {
// 构建一个通道
ch := make(chan int)
// 开启一个并发匿名函数
go func() {
// 从3循环到0
for i := 3; i >= 0; i-- {
// 发送3到0之间的数值
ch <- i
// 每次发送完时等待
time.Sleep(time.Second)
}
}()
// 遍历接收通道数据
for data := range ch {
// 打印通道数据
fmt.Println(data)
// 当遇到数据0时, 退出接收循环
if data == 0 {
break
}
}
}
执行代码,输出如下:
3
2
1
0
代码说明如下:
第 12 行,通过 make 生成一个整型元素的通道。
第 15 行,将匿名函数并发执行。
第 18 行,用循环生成 3 到 0 之间的数值。
第 21 行,将 3 到 0 之间的数值依次发送到通道 ch 中。
第 24 行,每次发送后暂停 1 秒。
第 30 行,使用 for 从通道中接收数据。
第 33 行,将接收到的数据打印出来。
第 36 行,当接收到数值 0 时,停止接收。(如果继续发送,由于接收 goroutine 已经退出,没有 goroutine 发送到通道,因此运行时将会触发宕机报错。)
接收方代码也可以写成下面这个样子:
// 开始无限循环等待数据
for {
// 从channel中获取一个数据
data := <-c
fmt.Println(data)
if data == 0 {
break
}
}
单向通道的使用
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
var chSendOnly chan<- int = ch
var chRecvOnly <-chan int = ch
go func() {
chSendOnly <- 1
}()
fmt.Println(<-chRecvOnly)
close(ch)
}
使用select+channel
select会阻塞直到其中一个case可以执行(或者有default的话会执行default)。通过在case中加入超时机制,可以让channel在没有后续操作时超时返回。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
quit := make(chan bool)
//新开一个协程
go func() {
for {
select {
case num := <-ch:
fmt.Println("num = ", num)
case <-time.After(3 * time.Second):
fmt.Println("超时")
quit <- true
}
}
}() //别忘了()
for i := 0; i < 5; i++ {
ch <- i
time.Sleep(time.Second)
}
<-quit // 阻塞,等待匿名函数超时返回
fmt.Println("程序结束")
}
输出为1.
channel基本使用
创建channel
创建普通channel
ch1 := make(chan int) // 创建一个整型类型的通道
ch2 := make(chan interface{}) // 创建一个空接口类型的通道, 可以存放任意格式
type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip)
创建单向channel
有时候处于安全需求需要限制channel的读写范围,可以将channel的读和写单独声明出来,这样向【读channel】中写入数据以及从【写channel】中读取数据就是非法的。
ch := make(chan int)
// 将该ch的输入端封装为chSendOnly,只能向chSendOnly写入数据
var chSendOnly chan<- int = ch
// 将该ch的输出端封装为chRecvOnly,只能向chRecvOnly写入数据
var chRecvOnly <-chan int = ch
创建buffered channel
package main
import "fmt"
func main() {
// 创建一个3个元素缓冲大小的整型通道
ch := make(chan int, 3)
// 查看当前通道的大小
fmt.Println(len(ch))
// 发送3个整型元素到通道
ch <- 1
ch <- 2
ch <- 3
// 查看当前通道的大小
fmt.Println(len(ch))
}
当buffered channel的缓冲区填满时,写入操作会阻塞;
当buffered channel的缓冲区为空时,读操作会阻塞;
接收channel的数据
// 阻塞式
data := <-ch
// 非阻塞式
data, ok := <-ch
// 循环式:会不断从信道中读取数据,直到信道关闭
for i := range ch {
fmt.Println(i)
}
使用阻塞式会一直等到通道传输数据给变量data后再继续往下执行;一般阻塞式使用场景会比较少,因为比较占CPU。
使用非阻塞式如果channel内没有数据,语句会继续执行;此时data为channel数据类型的零值,ok为false(表示未接收到数据)。
关闭channel
关闭channel
一般情况信道是不用关闭的,除非需要通知接收方(例如接收方在一个range循环里)。
close(ch) // 只有发送者可以关闭信道
判断channel是否已经关闭
x, ok := <-ch
ok为false时表示channel已经关闭
管道的数据类型
理论上,什么东西都可以通过channel传输
function channel
func abstractListener(fxChan chan func() string ) {
fxChan <- func() string {
return "Sent!"
}
}
func main() {
fxChan := make (chan func() string)
defer close(fxChan)
go abstractListener(fxChan)
select {
case rfx := <- fxChan:
msg := rfx()
fmt.Println(msg)
fmt.Println("Received!")
}
}
interface channel
type Messenger interface {
Relay() string
}
type Message struct {
status string
}
func (m Message) Relay() string {
return m.status
}
func alertMessages(v chan Messenger, i int) {
m := new(Message)
m.status = "Done with " + strconv.FormatInt(int64(i),10)
v <- m
}
func main () {
msg := make(chan Messenger)
for i:= 0; i < 10; i++ {
go alertMessages(msg,i)
}
select {
case message := <-msg:
fmt.Println (message.Relay())
}
<- msg
}