Go的channel

简介

在Go中,提倡使用通信来代替共享内存。也就是需要在goroutine之间共享资源的时候,使用channel来进行数据传递、交换。而channel就是用来传递数据的一个数据结构,同时也是一种特殊的类型,在任何时候,同时只能有一个goroutine访问channel进行发送和接收数据。channel总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序.

 

channel的声明

格式:var channel_ name chan type

  • channel_name:变量名

  • type:channel内的数据类型

  • chan:类型的空值是nil

例子:

// 声明一个传递整型的管道
var ch1 chan int
// 声明一个传递布尔类型的管道
var ch2 chan bool
// 声明一个传递int切片的管道
var ch3 chan []int

创建channel

channel引用类型,声明后需要配合make()函数进行创建

格式:

1.无缓冲  
make(chan 数据类型)
2.有缓冲  
make(chan 数据类型, 缓冲大小)

例子:

// 创建一个字符串类型的channel--无缓冲
ch1 := make(chan int)
// 创建一个能存储10个int类型的channel
ch2 := make(chan int, 10)
// 创建一个能存储4个bool类型的channel
ch3 := make(chan bool, 4)
// 创建一个能存储3个[]int切片类型的channel
ch4 := make(chan []int, 3)
// 创建一个空接口类型的channel,可以存放任意格式
ch5 := make(chan interface{})
// 创建指针型通道
ch6 := make(chan *P)

channel操作

通过<- 对channel进行发送数据和接收数据, <- 操作符代表数据的流向

//发送
//把传字符串"xixixi"到ch中
ch <- "xixixi"

//接收
//从ch中接收值并赋值给变量data
data := <-ch

// 从ch中接收值,忽略结果
<-ch  

 

不带缓存区的channel,在发送方发送数据的同时,必须要有接收方相应地接收数据。否则发送方会阻塞,直接接收方从通道中接收数据。

package main

func main() {
ch := make(chan string)

ch <- "xixixi"
}

运行代码会报错:

fatal error: all goroutines are asleep - deadlock!

 

关于接收数据

1.channel的数据接收可以借用for-range语句进行多个数据的接收操作。

2.接收会持续阻塞直到发送方发送数据。如果在接收方接收时,通道中没有发送方发送数据,则接收方也会发生阻塞,直到发送方发送数据为止。

package main

import "fmt"

func Sum(s []int, ch chan int) {
sum := 0

for _, v := range s {
sum += v
}

// 把sum发送到ch中
ch <- sum
}

func main() {
s := []int{1, 2, 3, 4, 5, 6}

ch := make(chan int)
go Sum(s[:len(s)/2], ch)
go Sum(s[len(s)/2:], ch)

//从ch中接收数据
m, n := <-ch, <-ch
fmt.Println(m, n, m+n)
}

 

带缓冲区的通道,允许发送方的数据发送和接收方的数据获取处于异步状态,在缓冲区没满的情况下,不需要有接收方立刻去取数据,不会阻塞。但是如果缓冲区一满,就会无法再发送数据,接收方不接受数据,就会产生阻塞,直到有接收方接收数据。

package main

func main() {
ch := make(chan string, 3)

ch <- "xixixi"
   ch <- "xixixi"
   ch <- "xixixi"
   
   //在缓冲区满后,如果继续发送数据,就会阻塞
   //ch <- "xixixi"
}

 

关于关闭channel

可以调用内置的close函数来关闭通道。

close(ch)

关于关闭通道需要注意的事情是,只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。

关闭后的通道有以下特点:

  1. 对一个关闭的通道再发送值就会导致panic。

  2. 对一个关闭的通道进行接收会一直获取值直到通道为空。

  3. 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。

  4. 关闭一个已经关闭的通道会导致panic。

 

单向通道

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

Go语言中提供了单向通道来处理这种情况。例如,我们把上面的例子改造如下:

func counter(out chan<- int) {
for i := 0; i < 100; i++ {
out <- i
}
close(out)
}

func squarer(out chan<- int, in <-chan int) {
for i := range in {
out <- i * i
}
close(out)
}
func printer(in <-chan int) {
for i := range in {
fmt.Println(i)
}
}

func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go counter(ch1)
go squarer(ch2, ch1)
printer(ch2)
}

其中,

  • chan<- int是一个只写单向通道(只能对其写入int类型值),可以对其执行发送操作但是不能执行接收操作;

  • <-chan int是一个只读单向通道(只能从其读取int类型值),可以对其执行接收操作但是不能执行发送操作。

在函数传参及任何赋值操作中可以将双向通道转换为单向通道,但反过来是不可以的。