管道的基本使用
一、管道的基本的定义/声明
var 变量名 chan 数据类型
举例:
1、var intChan chan int //用于存放int类型的数据
2、var mapChan chan map[int]string //用于存放map[int]string类型
3、var menChan chan Men //用于存放自定义类型Men类型的管道
4、var childChan chan *Child //用于存放自定义类型Child的指针类型的管道
5、var anyChan chan interface{} //用于存放任意类型的interface{}
channel是引用类型,所以他必须初始化之后才能写入数据,即需要make后才可以使用。还有就是channel是有类型的,声明int类型的channel只能写入int类型。
二、管道的初始化
channel要使用make进行初始化
var intChan chan int //声明int类型channel
intChan = make(chan int, 3) //创建了一个可以存放3个int类型的channel
通过fmt把intChan的值输出可以看到得到的是一个地址,这个地址指向了管道容器里面元素第一个元素的地址。这也就是为什么一个channel可以多个函数同时访问调用,多个函数可以操作同一个管道的原因(因为它是引用类型)。
三、数据写入与推出
首先是写入数据
//向管道写入数据
intChan <- 10
//或者以下方式也行
num:=20
intChan<-num
intChan <- 30
len(intChan)//获取intChan长度 3
cap(intChan)//获取intChan容量 3
管道不可以动态增长。,当我们给管道写入数据时,不能超过其容量。超过容量添加数据会报出提示:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]
但是我们可以取出数据之后在将新数据添加到管道里面。
var num2 int
num2 = <- intChan //这里取出的是第一个数据“10”,因为是队列,先进先出
num3 := <- intChan
<-intChan //取的时候也可以不传给任何变量
len(intChan) //这时取完后,获取intChan的长度 1
cap(intChan) //获取intChan容量 3保持不变
在没有使用协程的情况下,channel里面全部被取出并且没有新数据数据添加之后,仍然进行取数据操作,将会报错。
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]
下面说一个关于类型断言的小坑。在我之前的文章浅析Go中的MPG模式(二)——channel通讯最后基本介绍channel的时候写过:
channel本身是有数据类型的。string类型的channel只能存放string类型的数据。一般我们都设置为interface{}类型,这样取的时候可以不用进行类型断言去取,免除了一些不必要的麻烦。
下面通过一段代码来展示一下:
package main
import (
"fmt"
)
type Cat struct {
Name string
Age int
}
func main() {
allChan := make(chan interface{}, 2) //简写声明并初始化一个管道
cat := Cat{"miao", 4} //声明一个cat类型数据
allChan <- cat //将一个Cat类型的数据写入管道
newcat := <- allChan //将管道数据推出并
fmt.Println(newcat.Name) //打印newcat.Name
}
这个时候保存,编译器会检查出下图错误:
编译层面会认为newcat是一个interface类型的数据,而不是Cat类型,所以他没有Name这个字段。所以我们需要进行断言:(重点在第17行)
package main
import (
"fmt"
)
type Cat struct {
Name string
Age int
}
func main() {
allChan := make(chan interface{}, 2) //简写声明并初始化一个管道
cat := Cat{"miao", 4} //声明一个cat类型数据
allChan <- cat //将一个Cat类型的数据写入管道
newcat := <-allChan //将channel数据推出到newcat
a := newcat.(Cat) //newcat断言,转成Cat类型赋值到一个新的变量
fmt.Println(a.Name)
/*
输出一个字段也可以这么缩略写
fmt.Println((newcat.(Cat)).Name) //基于运算的优先级来看
*/
}
四、管道的关闭
(图片来自Go语言中文网)
我们可以通过close()来关闭管道,但是关闭的管道不能继续写入数据!
//这里接断言坑之前的代码内容
close(intChan)
intChan <- 50
这个时候就会报错(恐慌),发送输出给一个关闭的channel
如果管道内部还有数据未取出,那么即使管道关闭了也可以继续取出,直到为空不可取或者进程关闭。简而言之就是:管道关闭之后可取不可写
五、管道的遍历
管道写入和取出都可以使用遍历,channel支持for-range进行遍历。这里也推荐使用for-range来进行取出遍历,普通的for进行取出遍历会因为channel的length会发生变化而导致读取内容缺少等异常。
有两个细节需要注意:
- 在遍历时,如果channel没有关闭,会出现deadlock的错误
fatal error: all goroutines are asleep - deadlock!
- 在遍历时,如果channel已经关闭,会正常遍历数据,遍历完成后会退出遍历。
在我之前的文章浅析Go中的MPG模式(二)——channel通讯里面对channel基本介绍说过一个场景,传统的sync同步锁通过sleep来控制,不容易把握好协程运行的时间,可能会长可能会断,但是管道遍历的第二个细节正好是针对这一问题应运而生。可以让主线程知道协程什么时候关闭了。
所以对管道进行遍历取出数据时,需要线关闭管道,然后再进行取出。还有一个需要注意的,for-range正常遍历是有一个index和一个value,但是管道没有下标,所以只有一个value(管道是队列,先进先出,没有别的顺序)
未完待续…