浅析Go中的MPG模式(三)——channel基本使用

管道的基本使用

一、管道的基本的定义/声明

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会发生变化而导致读取内容缺少等异常。
有两个细节需要注意:

  1. 在遍历时,如果channel没有关闭,会出现deadlock的错误
    fatal error: all goroutines are asleep - deadlock!
  2. 在遍历时,如果channel已经关闭,会正常遍历数据,遍历完成后会退出遍历。

在我之前的文章浅析Go中的MPG模式(二)——channel通讯里面对channel基本介绍说过一个场景,传统的sync同步锁通过sleep来控制,不容易把握好协程运行的时间,可能会长可能会断,但是管道遍历的第二个细节正好是针对这一问题应运而生。可以让主线程知道协程什么时候关闭了。

所以对管道进行遍历取出数据时,需要线关闭管道,然后再进行取出。还有一个需要注意的,for-range正常遍历是有一个index和一个value,但是管道没有下标,所以只有一个value(管道是队列,先进先出,没有别的顺序)


未完待续…

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值