Go语言进阶四——Channel通道

314 阅读4分钟

我正在参加「掘金·启航计划」

1、channel

Go语言的并发模型是GSP,提倡通过通信共享内存而不是通过共享内存而实现通信。

如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。

Go语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先进先出(First In First Out)的队则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。

2、channel类型

channel是一种类型,一种引用类型。声明格式如下:

var 变量名 chan 元素类型

//举例:
var ch1  chan  int
var ch2  chan  bool
var ch3  chan  []int

3、创建channel

通道是引用类型,通道类型的空置是nil。

var ch chan int

fmt.Println(ch) // <nil>

声明通道后需要使用make函数初始化才能使用

make(chan 元素类型, [缓冲大小]) //缓冲大小是可选的

//例子:
make(chan int)
make(chan []int)

4、channel操作

通道有发送(send)、接收(receive)和关闭(close)三种操作。

发送和接收都是用<-符号。

ch := make(chan int)
ch <- 10  		// 将一个值发送到通道中
x  := <-ch 		// 从ch中接收值并赋值给变量x
<-ch 			// 从ch中接收值,忽略结果

内置函数close()关闭通道。

close(ch)

关闭通道后有以下特点:

  1. 对一个关闭的通道再发送值就会导致panic。
  2. 对一个关闭的通道进行接收会一直获取值直到通道为空。
  3. 对一个关闭的并且没有值得通道执行接收操作会得到对应类型的零值。
  4. 关闭一个已经关闭的通道会导致panic。

5、无缓冲的通道

无缓冲的通道又称为阻塞的通道。

func main(){
    ch := make(chan int)
    ch<-1
    fmt.Println("发送成功")fatal error: all goroutines are asleep - deadlock!
}

上面代码可以通过编译,但是在执行的时候会报错,fatal error: all goroutines are asleep - deadlock!;因为我们创建的是无缓冲的通道,无缓冲的通道只有在有人接收值得时候才能发送值,就像你的小区没有快递柜和代收点,快递员给你打电话必须要把这个物品送到你手中,简单说无缓冲通道必须有接收才能发送

func recv(c chan int){
    ret := <-c
    fmt.Println("接收成功", ret)
}

func main(){
    ch := make(chan int)
    go recv(ch) //启用goroutine从通道接收值
    ch <- 1
    fmt.Println("发送成功")
    
}

无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行,相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道发送一个值。

6、有缓冲通道

func main(){
    ch := make(chan int, 3)//创建一个容量为3的有缓冲通道
    ch <-1
    fmt.Println("发送成功")
}

只要通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量表示通道中能存放元素的数量,就是小区的快递柜子只有那么多格子,格子满了就装不下了,就阻塞了,等到别人取走一个快递员就能往里面放一个。

我们可以用内置的len()函数获取通道内元素的数量, 使用cap()函数获取通道的容量。

7、close()关闭通道

可以通过内置的函数close()关闭channel,如果你的通道不往里面存值或者取值的时候一定关闭通道。

func main(){
    ch := make(chan bool, 10)
    close(ch)
}

8、如何优雅的从通道循环取值

func main(){
    ch1 := make(chan int)
    ch2 := make(chan int)
    //开启goroutine将0-100发送到ch1
    go func(){
        for i:=0; i<100; i++ {
        	ch1<-i
        }
        close(ch1)
    }()
    
    //开启goroutine从ch1中接收值,并将该值得平方发送到ch2
    go func(){
        for {
            i, ok := <-ch1
            if !ok{
            	break
            }
            ch2<-i*i
        }
        close(ch2)
    }()
	
    //在主goroutine中冲ch2中接收值打印 
    for i := range ch2 { //通道关闭后会退出for range 循环
        fmt.Println(i)
    }
}

上面有两种方式在接收的时候判断通道是否被关闭,我们通常使用for range的方式。

9、单向通道

有时候我们将通道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用通道都会对齐进行限制,比如限制通道在函数中只能发送或者接收

chan<- int 是一个只能发送的单向通道,可以发送但不能接收。
<-chan int 是一个只能接收的单向通道,只能接收但不能发送。

10、通道总结

注意: 关闭已经关闭的channel会引发panic