知识点:
1.channel的定义和声明
2.带缓冲区/不带缓冲区 的channel
3.如何优雅的关闭channel
4.chan的死锁机制
5.channel应用场景
6.select 应用
channel的定义:
channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。
1.声明channel
2.引用类型
3.单向channel
var 变量名 chan 数据类型
channel和和map类似,channel也一个对应make创建的底层数据结构的引用。
当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil。定义一个channel时,也需要定义发送到channel的值的类型。
// 方法一:channel的创建赋值
var ch chan int;
ch = make(chan int);
// 方法二:短写法
ch:=make(chan int);
// 方法三:综合写法:全局写法!!!!
var ch = make(chan int);
单向chan
//定义只读的channel
read_only := make (<-chan int)
//定义只写的channel
write_only := make (chan<- int)
带缓冲区/不带缓冲区 的channel
带缓冲区channel:定义声明时候制定了缓冲区大小(长度),可以保存多个数据。
ch := make(chan int ,10) //带缓冲区 (只有当队列塞满时发送者会阻塞,队列清空时接受着会阻塞。)
不带缓冲区channel:只能存一个数据,并且只有当该数据被取出时候才能存下一个数据。
ch := make(chan int) //不带缓冲区
无缓冲channel详细解释:
1.一次只能传输一个数据
2.同一时刻,同时有 读、写两端把持 channel,同步通信。
如果只有读端,没有写端,那么 “读端”阻塞。
如果只有写端,没有读端,那么 “写端”阻塞。
读channel: <- channel
写channel: channel <- 数据
举一个形象的例子:
同步通信: 数据发送端,和数据接收端,必须同时在线。 —— 无缓冲channel
打电话。打电话只有等对方接收才会通,要不然只能阻塞
带缓channel详细解释:
举一个形象的例子:
异步通信:数据发送端,发送完数据,立即返回。数据接收端有可能立即读取,也可能延迟处理。 —— 有缓冲channel 不用等对方接受,只需发送过去就行。
发信息。短信。发送完就好,管他什么时候读信息。
如何优雅的关闭channel
注意:
读写操作注意:
- 向已关闭的channel发送数据,则会引发pannic;
- channel关闭之后,仍然可以从channel中读取剩余的数据,直到数据全部读取完成。
- 关闭已经关闭的channel会导致panic
- channel如果未关闭,在读取超时会则会引发deadlock异常
循环管道注意:
- 使用range循环管道,如果管道未关闭会引发deadlock错误。
- 如果采用for死循环已经关闭的管道,当管道没有数据时候,读取的数据会是管道的默认值,并且循环不会退出。
问题来了,如何知道channel是否关闭,如何优雅的关闭channel,
一个适用的原则是不要从接收端关闭channel,也不要关闭有多个并发发送者的channel。
关闭只读 channel 在语法上就彻底被禁止使用了。
读取channel的方式有两种:
close(ch)
一种方式:
value, ok := <- ch
ok是false,就表示已经关闭。
另一种方式,就是上面例子中使用的方式:
for value := range ch {
}
channel关闭之后,仍然可以从channel中读取剩余的数据,
直到数据全部读取完成,会跳出循环
select专题:
select是Golang在语言层面提供的多路IO复用的机制,其可以检测多个channel是否ready(即是否可读或可写)
总结select:
- select语句中除default外,每个case操作一个channel,要么读要么写
- select语句中除default外,各case执行顺序是随机的
- 如果select所有case中的channel都未ready,则执行default中的语句然后退出select流程
- select语句中如果没有default语句,则会阻塞等待任一case
- select语句中读操作要判断是否成功读取,关闭的channel也可以读取
举例:
(1)题目一:下面的程序输出是什么?
package main
import (
"fmt"
"time"
)
func main() {
chan1 := make(chan int)
chan2 := make(chan int)
go func() {
chan1 <- 1
time.Sleep(5 * time.Second)
}()
go func() {
chan2 <- 1
time.Sleep(5 * time.Second)
}()
select {
case <-chan1:
fmt.Println("chan1 ready.")
case <-chan2:
fmt.Println("chan2 ready.")
default:
fmt.Println("default")
}
fmt.Println("main exit.")
}
程序中声明两个channel,分别为chan1和chan2,依次启动两个协程,分别向两个channel中写入一个数据就进入睡眠。select语句两个case分别检测chan1和chan2是否可读,如果都不可读则执行default语句。
参考答案:
select中各个case执行顺序是随机的,如果某个case中的channel已经ready,则执行相应的语句并退出select流程,如果所有case中的channel都未ready,则执行default中的语句然后退出select流程
。另外,由于启动的协程和select语句并不能保证执行顺序,所以也有可能select执行时协程还未向channel中写入数据,所以select直接执行default语句并退出。所以,以下三种输出都有可能:
可能的输出一:
chan1 ready.
main exit.
可能的输出二:
chan2 ready.
main exit.
可能的输出三:
default
main exit.
(2)题目二:下面的程序执行到select时会发生什么?
package main
import (
"fmt"
"time"
)
func main() {
chan1 := make(chan int)
chan2 := make(chan int)
writeFlag := false
go func() {
for {
if writeFlag {
chan1 <- 1
}
time.Sleep(time.Second)
}
}()
go func() {
for {
if writeFlag {
chan2 <- 1
}
time.Sleep(time.Second)
}
}()
select {
case <-chan1:
fmt.Println("chan1 ready.")
case <-chan2:
fmt.Println("chan2 ready.")
}
fmt.Println("main exit.")
}
程序中声明两个channel,分别为chan1和chan2,依次启动两个协程,协程会判断一个bool类型的变量writeFlag来决定是否要向channel中写入数据,由于writeFlag永远为false,所以实际上协程什么也没做。select语句两个case分别检测chan1和chan2是否可读,这个select语句不包含default语句。
参考答案:select会按照随机的顺序检测各case语句中channel是否ready,如果某个case中的channel已经ready则执行相应的case语句然后退出select流程,如果所有的channel都未ready且没有default的话,则会阻塞等待各个channel。所以上述程序会一直阻塞。
(3)题目三:下面程序有什么问题?
package main
import (
"fmt"
)
func main() {
chan1 := make(chan int)
chan2 := make(chan int)
go func() {
close(chan1)
}()
go func() {
close(chan2)
}()
select {
case <-chan1:
fmt.Println("chan1 ready.")
case <-chan2:
fmt.Println("chan2 ready.")
}
fmt.Println("main exit.")
}
程序中声明两个channel,分别为chan1和chan2,依次启动两个协程,协程分别关闭两个channel。select语句两个case分别检测chan1和chan2是否可读,这个select语句不包含default语句。
参考答案:select会按照随机的顺序检测各case语句中channel是否ready,考虑到已关闭的channel也是可读的,所以上述程序中select不会阻塞,具体执行哪个case语句具是随机的。
有疑问加站长微信联系(非本文作者)