18

go channel

 4 years ago
source link: https://studygolang.com/articles/28765
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

知识点:

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异常

uErmQbq.png!web

循环管道注意:

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

BJNJRnM.png!web

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

uQneqqq.png!web

问题来了,如何知道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知识

FveQFjN.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK