前言
goroutine是go语言程序的并发执行的基本单元,多个goroutine的通信是需要依赖channel,叫做信道
1.信道的定义与使用
每个信道只能传递一种数据类型的数据,所以声明信道的时候,需要指定数据类型(string、int等)
信道实例 := make(chan 信道类型)
假如我要创建一个可以传递int数据类型的信道
//定义信道 pipline := make(chan int)
信道的数据操作,无非就是两种:发送数据和读取数据
// 往信道中发送数据 pipline<- 200 // 从信道中取出数据,并赋值给mydata mydata := <-pipline
信道用完了,可以对其进行关闭,避免有人一直在等待
close(pipline)
对于一个已经关闭的信道 如果再去关闭是会报错的,所以我们需要判断一下 信道 是不是被关闭
当从信道读取数据时,可以有多个返回值,其中第二个可以表示信道是否被关闭,如果已经被关闭就返回false,如果还没被关闭返回true
x, ok := <-pipline
2.信道的容量和长度
一般创建信道都是使用make函数,make函数接收两个参数
第一个参数: 必填 必定信道类型
第二个参数: 选填 不填默认为0,指定信道的容量(可以缓存多少数据)
对于信道的容量很重要:
- 当容量为0时,说明信道中不能存放数据,在发送数据时,必须要求立马有人接收,否则会报错。此时的信道称之为无缓冲信道
- 当容量为1时,说明信道中只能缓存一个数据,如果信道中已经有一个数据,此时再往里发送数据,会造成阻塞;可以利用信道这点做锁
- 当容量大于1时,信道中可以存放多个数据,可以用于多个协程间的通信管道,共享资源
我们现在知道 信道就是一个容器
如果将它比作一个纸箱子
- 它可以装10本书,代表其容量为10
- 当前只装了1本书,代表其当前长度为1
信道的容量可以用cap()函数获取,信道的长度可以用len()函数来获取
package main import "fmt" func main() { pipline := make(chan int, 10) fmt.Printf("信道可缓冲 %d 个数据 ", cap(pipline)) pipline<- 1 fmt.Printf("信道中当前有 %d 个数据", len(pipline)) }
输出如下
信道可缓冲 10 个数据 信道中当前有 1 个数据
3.缓冲信道和无缓冲信道
按照是否可缓冲数据可分为:缓冲信道和无缓冲信道
缓冲信道
允许信道里存储一个或多个数据,这意味着,设置了缓冲区后,发送端和接收端可以处于异步的状态
pipline := make(chan int, 10)
无缓冲信道
在信道里无法存储数据,这意味着,接收端必须先于发送端准备好,以确保你发送完数据和,有人立马接收数据,否则发送端就会造成阻塞
原因很简单,信道中无法存储数据;也就是说发送端和接收端是同步运行的
pipline := make(chan int) // 或者 pipline := make(chan int, 0)
4.双向通道和单向通道
通常情况下,我们定义的都是双向信道,可以发送数据,也可以接收数据
但有时候,我们希望对信道的数据流做一些控制,比如这个信道只能接收数据或者这个信道只能发送数据
因此就有了双向信道和单向信道两种分类
4.1双向信道
默认情况下你定义的信道都是双向的,比如下面代码
import ( "fmt" "time" ) func main() { pipline := make(chan int) go func() { fmt.Println("准备发送数据: 100") pipline <- 100 }() go func() { num := <-pipline fmt.Printf("接收到的数据是: %d", num) }() // 主函数sleep,使得上面两个goroutine有机会执行 time.Sleep(1) }
4.2单向信道
可以细分为只读信道和只写信道
定义只度信道
var pipline = make(chan int) type Receiver = <-chan int // 关键代码:定义别名类型 var receiver Receiver = pipline
定义只写信道
var pipline = make(chan int) type Sender = chan<- int // 关键代码:定义别名类型 var sender Sender = pipline
仔细观察区别在于<-符号在关键字chan的左边还是右边
-
<-chan
表示这个信道,只能从里发出数据,对于程序来说就是只读 -
chan<-
表示这个信道,只能从外面接收数据,对于程序来说就是只写
为什么要先定义一个双向信道,在定义一个单向信道呢,比如这样写
import ( "fmt" "time" ) //定义只写信道类型 type Sender = chan<- int //定义只读信道类型 type Receiver = <-chan int func main() { var pipline = make(chan int) go func() { var sender Sender = pipline fmt.Println("准备发送数据: 100") sender <- 100 }() go func() { var receiver Receiver = pipline num := <-receiver fmt.Printf("接收到的数据是: %d", num) }() // 主函数sleep,使得上面两个goroutine有机会执行 time.Sleep(1) }
信道本身就是为了传输数据而存在的,如果只有接收者或者只有发送者,那信道就变成了只入不出或者只出不入,那就没意义了.
5.遍历信道
遍历信道,可以搭配使用for搭配range关键字,在range时,要确保信道处于关闭状态,否则循环会阻塞
import "fmt" func fibonacci(mychan chan int) { n := cap(mychan) x, y := 1, 1 for i := 0; i < n; i++ { mychan <- x x, y = y, x+y } // 记得 close 信道 // 不然主函数中遍历完并不会结束,而是会阻塞。 close(mychan) } func main() { pipline := make(chan int, 10) go fibonacci(pipline) for k := range pipline { fmt.Println(k) } }
当容量为1时,说明信道只能缓存一个数据,若信道中已有一个数据,此时再往里发送数据,会造成程序阻塞。 利用这点可以利用信道来做锁