您的位置 首页 golang

【go】 channel

知识点:
1.channel的定义和声明
2.带缓冲区/不带缓冲区 的channel
3.如何优雅的关闭channel
4.chan的死锁机制
5.channel应用场景
6.select 应用


channel的定义:

channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。

1.声明channel2.引用类型3.单向channelvar 变量名 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//定义只读的channelread_only := make (<-chan int) //定义只写的channelwrite_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异常

clipboard.png

循环管道注意:

  • 使用range循环管道,如果管道未关闭会引发deadlock错误。

image.png

  • 如果采用for死循环已经关闭的管道,当管道没有数据时候,读取的数据会是管道的默认值,并且循环不会退出。

clipboard1.png

问题来了,如何知道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 mainimport (    "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.

可能的输出三:

defaultmain exit.

(2)题目二:下面的程序执行到select时会发生什么?

package mainimport (    "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 mainimport (    "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语句具是随机的。


文章来源:智云一二三科技

文章标题:【go】 channel

文章地址:https://www.zhihuclub.com/421.shtml

关于作者: 智云科技

热门文章

发表回复

您的电子邮箱地址不会被公开。

网站地图