说说channel哪些事-上篇

channel是什么

channel中文翻译为通道,它是Go语言内置的数据类型,使用channel不需要导入任何包,像int/float一样直接使用。它主要用于goroutine之间的消息传递和事件通知。
在Go语言中流传着一句话,就是说不要通过共享内存来通信,而是应该通过通信来共享内存。

Don’t communicate by sharing memory, share memory by communicating.

上面这句话也包含了通信的两种方式:1是通过共享内存 2是通过通信。channel来源灵感要追溯到CSP(communicating sequential process)模型,CSP是用来描述并发系统中进行交互的一种模式。CSP概念最早由Tony Hoare提出,对这个人我们可能很陌生。但说到快速排序算法(Quicksort),你一定不陌生,快速排序太经典了,对Tony Hoare就是快速排序算法的作者。CSP允许使用进程组件来描述系统,各个组件独立运行,它们之间通过消息传递的方式进行通信。
Go语言中使用channel实现CSP思想,整个channel实现只有短短的700行代码,非常精炼,非常值得一读。channel和goroutine的结合,为并发编程提供了优雅的、便利的、 与传统并发控制不同的方案,并产生了在Go中特有的并发模式。

为什么需要channel

channel不是必需的,不用channel也可以完成goroutine之间的消息传递和事件通知,比如通过共享变量的方式。但使用了channel,会大大提升开发的效率,因为channel是并发安全的,channel的设计与goroutine之间完美配合,降低了并发编程的难度,减少了data race产生,大幅提升了生产力,嗯,程序员的福音。

channel基本用法

channel创建

channel有3种类型,分别为只能接收、只能发送、既能接收又能发送。定义方法如下:

func main() {
	// readChan只能接收
	var readChan <-chan int
	// writeChan只能发送
	var writeChan chan<- int
	// rwChan既能接收又能发送
	var rwChan chan int

	fmt.Println(readChan == nil)  //true
	fmt.Println(writeChan == nil) //true
	fmt.Println(rwChan == nil)    //true
}

channel中的元素对类型没有限制,任意类型都可以,所以元素的类型也可以是chan类型。哪怎么判断<-属于哪个chan呢? <-的匹配规则是总是尽量与它左边的chan结合(the <-operator associates with the leftmost chan possible)。所以writeIntChan是一个只能发送的chan,发送的元素为chan int(双向的int型chan), readIntChan是一个只能接收的chan,发送的元素为chan int, rwIntChan是一个双向的chan, 发送的元素为chan int.

func elemIsChan(){
	// chan元素类型也可以是channel
	var writeIntCahn chan<- chan int
	var readIntChan <- chan chan int
	var rwIntChan chan chan int
}

channel定义完成之后,并不能直接使用,需要初始化。上面readChan/writeChan/rwChan都是未被初始化的,它们的值都是nil.
channel用make初始化,不能用new方法。make创建的时候可以传一个数字,表示创建有缓冲区的chan, 如果没有设置,它是无缓冲区的。

func makeChan(){
	// 无缓冲区的chan
	unbufferedCh:=make(chan int)
	// 有缓冲区的chan,可以缓存10个int数据
	bufferedCh:=make(chan int,10)
}
向channel中发送数据

往chan中发送一个数据使用“ch<-”,下面的操作往ch发送一个int数据200. ch是一个双向的chan,可以往里面发送数据。 ch2是一个只能发送的ch,也可以往里面发送数据。

func sendDataToChan() {
	ch := make(chan int, 1)
	ch <- 200

	ch2 := make(chan<- int, 1)
	ch2 <- 100
}
从channel中取数据

使用<-ch从chan中接收一条数据,ch是一个双向chan或者只读chan.下面的操作从ch读取数据,ch2是一个只读chan,也可以进行读取数据的操作。
chan接收操作,可以返回一个值可以可以返回两个值,第一个值是返回chan中的元素,第二个值是个bool类型,表示是否成功地从chan中读取到了一个值。如果chan已经被关闭而且所有的数据都已经读完,第一个值将是零值。需要注意的是,如果从chan读取到一个零值,可能是sender真实的在发送零值,也有可能是chan被关闭且没有缓存的元素了产生的零值。

func recvDataFromChan() {
	// 双向chan
	ch := make(chan int, 1)
	<-ch

	// 只读chan
	ch2 := make(<-chan int, 1)
	<-ch2
	
	// 只读取数据
	_=<-ch2
	
	// 读取数据并想知道ch2是否已关闭
	_,_=<-ch2
}
关闭channel

close(ch)直接将一个chan关闭,需要注意的是,如果一个chan未被初始化,也就是没有执行make操作,是不能close的,否则引发panic.还有就是不能重复关闭一个chan,重复关闭一个chan也会产生panic.还有就是不能往一个关闭的chan中发送数据,也会产生panic. 最后一个需要注意的是不能close一个只读的chan,直接编译不会通过。

func closeNilChan() {
	var ch chan int
	close(ch)
	
	//panic:close of nil channel
}

func closeOnlyReadChan() {
	var ch <-chan int
	ch = make(<-chan int)
	close(ch)
	// invalid operation: close(ch) (cannot close receive-only channel)
}

其他操作

Go内置的cap、len都可以操作chan,cap返回chan的容量,len返回chan中缓存的还未被取走的元素数量。还可以在select case中向chan发送数据或从chan中接收数据。
for-range操作chan,从chan读取数据。当ch被close之后,for-rang循环都会结束。

func forRangeChan() {
	ch := make(chan int, 1)
	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		// ch被close后,for-range会结束循环
		for v := range ch {
			fmt.Println(v)
		}
		fmt.Println("for-range end")
	}()

	ch <- 1
	close(ch)

	wg.Wait()
}

func forRangeChan2() {
	ch := make(chan int, 1)
	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		// ch被close后,for-range结束循环
		for range ch {
		}
		fmt.Println("for-range end")
	}()

	close(ch)

	wg.Wait()
}

channel实现原理

下面介绍channel底层是怎么实现的,源码在runtime/chan.go文件中。chan分配的是一个hchan的数据结构,定义如下:

const (
	maxAlign = 8
	// hchanSize为8的倍数,如果不是调整到下一个最小的8的倍数,假如hchan大小9,hchanSize为16
	hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1))
	debugChan = false
)

type hchan struct {
	// 队列中元素的个数
	qcount uint
	// 环形队列的大小
	dataqsiz uint
	// 指向含有dataqsiz个元素的数组的地址
	buf unsafe
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值