channel基础
channel用于goroutines之间的通信,让它们之间可以进行数据交换。像管道一样,一个goroutine_A向channel_A中放数据,另一个goroutine_B从channel_A取数据。
channel是指针类型的数据类型,通过make来分配内存。例如:
ch := make(chan int)
这表示创建一个channel,这个channel中只能保存int类型的数据。也就是说一端只能向此channel中放进int类型的值,另一端只能从此channel中读出int类型的值。
需要注意,chan TYPE
才表示channel的类型。所以其作为参数或返回值时,需指定为xxx chan int
类似的格式。
向ch这个channel放数据的操作形式为:
ch <- VALUE
从ch这个channel读数据的操作形式为:
<-ch // 从ch中读取一个值
val = <-ch
val := <-ch // 从ch中读取一个值并保存到val变量中
val,ok = <-ch // 从ch读取一个值,判断是否读取成功,如果成功则保存到val变量中
其实很简单,当ch出现在<-
的左边表示send,当ch出现在<-
的右边表示recv。
例如:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go sender(ch) // sender goroutine
go recver(ch) // recver goroutine
time.Sleep(1e9)
}
func sender(ch chan string) {
ch <- "malongshuai"
ch <- "gaoxiaofang"
ch <- "wugui"
ch <- "tuner"
}
func recver(ch chan string) {
var recv string
for {
recv = <-ch
fmt.Println(recv)
}
}
输出结果:
malongshuai
gaoxiaofang
wugui
tuner
上面激活了一个goroutine用于执行sender()函数,该函数每次向channel ch中发送一个字符串。同时还激活了另一个goroutine用于执行recver()函数,该函数每次从channel ch中读取一个字符串。
注意上面的recv = <-ch
,当channel中没有数据可读时,recver goroutine将会阻塞在此行。由于recver中读取channel的操作放在了无限for循环中,表示recver goroutine将一直阻塞,直到从channel ch中读取到数据,读取到数据后进入下一轮循环由被阻塞在recv = <-ch
上。直到main中的time.Sleep()指定的时间到了,main程序终止,所有的goroutine将全部被强制终止。
因为receiver要不断从channel中读取可能存在的数据,所以receiver一般都使用一个无限循环来读取channel,避免sender发送的数据被丢弃。
channel的属性和分类
每个channel都有3种操作:send、receive和close
- send:表示sender端的goroutine向channel中投放数据
- receive:表示receiver端的goroutine从channel中读取数据
- close:表示关闭channel
- 关闭channel后,send操作将导致painc
- 关闭channel后,recv操作将返回对应类型的0值以及一个状态码false
- close并非强制需要使用close(ch)来关闭channel,在某些时候可以自动被关闭
- 如果使用close(),建议条件允许的情况下加上defer
- 只在sender端上显式使用close()关闭channel。因为关闭通道意味着没有数据再需要发送
- 关闭channel后,send操作将导致painc
例如,判断channel是否被关闭:
val, ok := <-counter
if ok {
fmt.Println(val)
}
channel分为两种:unbuffered channel和buffered channel
- unbuffered channel:阻塞、同步模式
- sender端向channel中send一个数据,然后阻塞,直到receiver端将此数据receive
- receiver端一直阻塞,直到sender端向channel发送了一个数据
- sender端向channel中send一个数据,然后阻塞,直到receiver端将此数据receive
- buffered channel:非阻塞、异步模式
- sender端可以向channel中send多个数据(只要channel容量未满),容量满之前不会阻塞
- receiver端按照队列的方式(FIFO,先进先出)从buffered channel中按序receive其中数据
- sender端可以向channel中send多个数据(只要channel容量未满),容量满之前不会阻塞
buffered channel有两个属性:容量和长度:和slice的capacity和length的概念是一样的
- capacity:表示bufffered channel最多可以缓冲多少个数据
- length:表示buffered channel当前已缓冲多少个数据
- 创建buffered channel的方式为
make(chan TYPE,CAP)
unbuffered channel可以认为是容量为0的buffered channel,所以每发送一个数据就被阻塞。注意,不是容量为1的buffered channel,因为容量为1的channel,是在channel中已有一个数据,并发送第二个数据的时候才被阻塞。
换句话说,send被阻塞的时候,其实是没有发送成功的,只有被另一端读走一个数据之后才算是send成功。对于unbuffered channel来说,这是send/recv的同步模式。
实际上,当向一个channel进行send的时候,先关闭了channel,再读取channel时会发现错误在send,而不是recv。它会提示向已经关闭了的channel发送数据。
func main() {
counter := make(chan int)
go func() {
counter <- 32
}()
close(counter)
fmt.Println(<-counter)
}
输出报错:
panic: send on closed channel
所以,在Go的内部行为中,send和recv是一个整体行为,数据未读就表示未send成功。
死锁(deadlock)
当channel的某一端(sender/receiver)期待另一端的(receiver/sender)操作,另一端正好在期待本端的操作时,也就是说两端都因为对方而使得自己当前处于阻塞状态,这时将会出现死锁问题。
比如,在main函数中,它有一个默认的goroutine,如果在此goroutine中创建一个unbuffered channel,并在main goroutine中向此channel中发送数据并