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。
当ch出现在<-的左边表示send,当ch出现在<-的右边表示recv。
一个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发送的数据被丢弃。
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 <- "aa"
ch <- "bb"
ch <- "cc"
ch <- "dd"
}
func recver(ch chan string) {
var recv string
for {
recv = <-ch
fmt.Println(recv)
}
}
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是否被关闭:
val, ok := <-counter
if ok {
fmt.Println(val)
}
unbuffered channel和buffered channel
- unbuffered channel:阻塞、同步模式
- sender端向channel中send一个数据,然后阻塞,直到receiver端将此数据receive
- receiver端一直阻塞,直到sender端向channel发送了一个数据
- buffered channel:非阻塞、异步模式
- sender端可以向channel中send多个数据(只要channel容量未满),容量满之前不会阻塞
- receiver端按照队列的方式(FIFO,先进先出)从buffered channel中按序receive其中数据
可以认为阻塞和不阻塞是由channel控制的,无论是send还是recv操作,都是在向channel发送请求:
- 对于unbuffered channel,sender发送一个数据,channel暂时不会向sender的请求返回ok消息,而是等到receiver准备接收channel数据了,channel才会向sender和receiver双方发送ok消息。在sender和receiver接收到ok消息之前,两者一直处于阻塞。
使用无缓冲通道在goroutine之间同步
- 对于buffered channel,sender每发送一个数据,只要channel容量未满,channel都会向sender的请求直接返回一个ok消息,使得sender不会阻塞,直到channel容量已满,channel不会向sender返回ok,于是sender被阻塞。对于receiver也一样,只要channel非空,receiver每次请求channel时,channel都会向其返回ok消息,直到channel为空,channel不会返回ok消息,receiver被阻塞。
使用有缓冲通道在goroutine之间同步
buffered 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的同步模式。而buffered channel则是在每次发送数据到通道的时候,(通道)都向发送者返回一个消息,容量未满的时候返回成功的消息,发送者因此而不会阻塞,容量已满的时候因为已满而迟迟不返回消息,使得发送者被阻塞。
实际上,当向一个channel进行send的时候,先关闭了channel,再读取channel时会发现错误在send,而不是recv。它会提示向已经关闭了的channel发送数据。
package main
import "fmt"
func main() {
counter := make(chan int)
go func() {
counter <- 32
}()
close(counter)
fmt.Println(<-counter)
}
所以,在Go的内部行为中,send和recv是一个整体行为,数据未读就表示未send成功