go语言之channel

channel(通道)

如果说goroutine是Go语言的并发执行体,那channel则是goroutine之间的通信机制。channel是进程内的通信机制,是类型相关的,即只能绑定一种数据类型。
----通道c1可以接收任意格式的数据,是因为绑定的是interface{},任何类型都实现了空接口,因此可以传输任意格式的数据。
----通道c2报错,是因为绑定的是int类型,你发送一个string类型肯定是不支持的。

func TestChannel2() {
	c1 := make(chan interface{}, 2)
	c2 := make(chan int, 2)
	go func() {
		c1 <- "lizhih"
		c1 <- 2

		c2 <- 2
        //报错:'"lizhih"' (type string) cannot be 
        //represented by the type int
		c2 <- "lizhih"
	}()

发送和接受通道:刚开始接触的时候,很容易被chan<-和<-chan搞混。可以将chan看作通道,<-看作数据。chan<-:数据写入通道,就是发送通道了;<-chan:数据出通道,就是接收通道了。

无缓冲和有缓冲的通道

	//有缓冲的通道,2个int数据
	var c3 chan int = make(chan int, 2)
	//无缓冲的通道
	c4:=make(chan int)

----无缓冲通道:在接收前,没能力保存数据的通道
这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。也称之为“同步通道”
----有缓冲通道:在接收前,有能力保存一个或者多个值的通道,取决于make初始化的大小;
这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。也称之为“异步通道”

内部的数据结构

路径:runtime/chan.go

type hchan struct {
	qcount   uint           // total data in the queue。channel中的元素个数
	dataqsiz uint           // size of the circular queue。channel中的循环队列的长度,创建之后就不会改变
	buf      unsafe.Pointer // points to an array of dataqsiz elements。指向长度为dataqsize的数组
	elemsize uint16  //元素大小
	closed   uint32  //通道关闭状态,1-关闭,0-开启
	elemtype *_type // element type。元素类型
	sendx    uint   // send index。指向buf中存放发送的索引
	recvx    uint   // receive index。指向buf中存放接收的索引
	recvq    waitq  // list of recv waiters。等待channel的goroutine接收队列
	sendq    waitq  // list of send waiters。等待channel的goroutine发送队列

	// lock protects all fields in hchan, as well as several
	// fields in sudogs blocked on this channel.
	//
	// Do not change another G's status while holding this lock
	// (in particular, do not ready a G), as this can deadlock
	// with stack shrinking.
	lock mutex
}

https://halfrost.com/go_channel/
sendq和recvq:存储了由于缓冲空间不足而阻塞的goroutine列表。
rumtime.sudog:表示一个在等待列表中给goroutine,是一个双向指针结构,成员elem就是存放数据的变量(elem unsafe.Pointer)。
unsafe.Pointer:指向任意类型的指针
(type Pointer *ArbitraryType—任意类型指针)
buf的值(makechan函数内):
1)没有缓冲,则只有chan的内存
2)元素类型不包含指针,则chan内存和buf内存是一片连续的内存。

		c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
		c.buf = add(unsafe.Pointer(c), hchanSize)

3)元素类型包含指针类型,则chan内存和buf内存不连续,分开创建

		c = new(hchan)
		c.buf = mallocgc(mem, elem, true)

发送数据(通道写chan <-)

核心逻辑:chansend
1)异常检查
channel被GC回收,或者当前状态是close,则send的时候会抛异常。
2)发送数据,先对通道上锁(每个通道结构体都有一个锁)

1.同步发送:如果等待接收队列中有阻塞的接收方,则跳过缓冲区(可以理解为缓冲区和待发发送的队列都是空的),直接奖数据拷贝到接收goroutine的内存中;
     sendDirect:将发送的数据拷贝到接收的goroutine中(memmove);
     goready:将接收的goroutine状态设置为_Grunnable,下一轮调度时会唤醒这个接收的 goroutine;
2.异步发送:是指当前无阻塞的接收方,且缓存还有剩余空间。则将数据缓存到缓冲区,同时记得更新sendx索引;
    chanbuf:获取当前缓冲区发送索引的地址
    typedmemmove:将发送的数据拷贝到buf缓冲区
3.阻塞发送:是指当前无阻塞的接收方,且缓存已满,这个时候将发送方放到等待发送队列中,此时发送的goroutine是阻塞的状态;
	acquireSudog:创建一个sudog,存放当前goroutine状态和数据
	c.sendq.enqueue:压入等待发送队列中
	gopark:将goroutine状态设置为waitReasonChanSend

接收数据(<-chan)

核心逻辑:chanrecv
1)异常检查
chan是否为nil,为nil则会抛出异常,并阻塞

2)接收数据,先对通道上锁

1.同步接收(recv)等待发送队列有阻塞的goroutine
    1.1 判断是否有缓冲区,没有则直接从等待发送队列中拷贝数据
    1.2 有缓冲区的,但是发送队列不为空,说明缓冲区已满,chanbuf拿到缓冲区接收索引指针。这里有两次拷贝:
          --从缓冲区到接收goroutine
          --将等待发送的goroutine到缓冲区
   1.3 goready
2.异步接收
    缓冲区有数据,且等待队列没有发送方。
3.阻塞接收
	缓冲区没有数据的时候,发送队列也没有待发送的goroutine。

关闭通道

1)异常检查
关闭一个已关闭的通道,或者通道是nil,会抛异常
2)释放待发送队列和待接收队列中的goroutine,并将状态从_Gwaiting改成_Grunnable。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值