go channel

知识点:

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的方式有两种:
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语句具是随机的。

欢迎关注我们的微信公众号,每天学习Go知识