golang深刻剖析——channel

1 概念

channel 是一个通道,用于端到端的数据传输,这有点像我们平常使用的消息队列,只不过 channel 的发送方和接受方是 goroutine 对象,属于内存级别的通信。

2 分类

3 操作

在深入了解 channel 的底层之前,我们先来看看 channel 的常用用法。

3.1 channel 的创建

3.1.1 无缓冲channel
 ch := make(chan int)

对于无缓冲的 channel,一旦有 goroutine 往 channel 写入数据,那么当前的 goroutine 会被阻塞住,直到有其他的 goroutine 消费了 channel 里的数据,才能继续运行写入数据。

3.1.1 带缓冲channel

还有另外一种是有缓冲的 channel,它的创建是这样的:

ch := make(chan int, 10)

其中,第二个参数表示 channel 可缓冲数据的容量。只要当前 channel 里的元素总数不大于这个可缓冲容量,则当前的 goroutine 就不会被阻塞住。

另外,我们也可以声明一个 nil 的 channel,只是创建这样的 channel 没有意义,读、写 channel 都将会被阻塞住。一般 nil channel 用在 select 上,让 select 不再从这个 channel 里读取数据,如下用法:

ch1 := make(chan int)
ch2 := make(chan int)
go func() {
	if !ok { // 某些原因,设置 ch1 为 nil
 		ch1 = nil
 	}
 }()
for {
	select {
		case <-ch1: // 当 ch1 被设置为 nil 后,将不会到达此分支了。
   			doSomething1()
  		case <-ch2:
  			doSomething2()
  	}
}

3.2 channel的读写

ch := make(chan int,10)
ch<-1               //向管道中写入数据
readdata:=<-ch      //从管道中读取数据

3.3 channel的关闭(****)

ch := make(chan int,10)
	...
close(ch)

面试题1:当关闭channel之和再操作channel会发生什么?
解:
写数据:则程序会直接 panic 退出;
读数据:(1) 有数据:读到关闭之前写入的数据;
    (2) 无数据:将得到零值(无效数据),即对应类型的默认值。

面试题2:对关闭的chan进行读操作,如何保证读取的数据是有效的而非无效零值呢?
解:
使用_,ok:=<-chan 判断是否关闭:

if v, ok := <- ch; ok {
    fmt.Println(v)  //ok为true表示读取管道成功,数据是有效的
}

3.4 channel 和 select

在写程序时,有时并不单单只会和一个 goroutine 通信,当我们要进行多 goroutine 通信时,则会使用 select 写法来管理多个 channel 的通信数据:

 ch1 := make(chan struct{})
 ch2 := make(chan struct{})

 // ch1, ch2 发送数据
 go sendCh1(ch1)
 go sendCh1(ch2)

 // channel 数据接受处理
 for {
 	select {
  	case <-ch1:
   		doSomething1()
  	case <-ch2:
   		doSomething2()
  	}
 }

3.5 channel 的死锁

前面提到过,往 channel 里读写数据时是有可能被阻塞住的,一旦被阻塞,则需要其他的 goroutine 执行对应的读写操作,才能解除阻塞状态。

然而,阻塞后一直没能发生调度行为,没有可用的 goroutine 可执行,则会一直卡在这个地方,程序就失去执行意义了。此时 Go 就会报 deadlock 错误,如下代码:

package main
func main() {
  ch := make(chan int)
	ch<-10
 }

运行结果:

  fatal error: all goroutines are asleep - deadlock!

因此,在使用 channel 时要注意 goroutine 的有发有取,避免 goroutine 死锁

4 channel 底层原理(*****)

前面提及过 channel 创建后返回了 hchan 结构体,现在我们来研究下这个结构体,它的主要字段如下:

type hchan struct {
  //channel分为无缓冲和有缓冲两种。
  //对于有缓冲的channel存储数据,借助的是如下循环队列的结构
	qcount   uint           // 循环队列中的元素数量
	dataqsiz uint           // 循环队列的长度
	buf      unsafe.Pointer // 指向底层循环队列的指针
	elemsize uint16         //能够收发元素的大小
  

	closed   uint32        //channel是否关闭的标志
	elemtype *_type        //channel中的元素类型
  
  //有缓冲channel内的缓冲数组会被作为一个“环型”来使用。
  //当下标超过数组容量后会回到第一个位置,所以需要有两个字段记录当前读和写的下标位置
	sendx    uint   // 下一次发送数据的下标位置
	recvx    uint   // 下一次读取数据的下标位置
  
  //当循环数组中没有数据时,收到了接收请求,那么接收数据的变量地址将会写入读等待队列
  //当循环数组中数据已满时,收到了发送请求,那么发送数据的变量地址将写入写等待队列
	recvq    waitq  // 读等待队列
	sendq    waitq  // 写等待队列


	lock mutex //互斥锁,保证读写channel时不存在并发竞争问题
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

篱落~~成殇~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值