,Golang并发的基石大多数编程语言的并发编程模型都是基于线程和内存同步的,而Golang的并发编程模型被goroutine和channel所取代。Goroutine用于执行并发任务,channel用于并发控制和goroutine通信。这一次,跟着一个demo一起来探寻通道底层的奥秘吧。channel数据结构:typehchanstruct{//chan中元素的个数qcountuint//chan维护的数组长度dataqsizuint//指向维护数组的指针bufunsafe.Pointer//chan中元素的大小elemsizeuint16//chan是否Closedflagcloseduint32//chan中的元素类型elemtype*_type//发送元素在循环数组中的索引sendxuint//接收元素在循环数组中的索引recvxuint//goroutine队列waitingtoreceiverecvqwaitq//等待goroutine队列sendqwaitq//保证读写chan是一个原子操作lockmutex}数据结构图:示例代码:描述:channel容量为3,设置6个send,构造goroutineblockedstate,8receive,构造接收G阻塞状态场景,方便探索通道的运行过程。funcmain(){ch:=make(chanint,3)CheckChannel(ch)}funcCheckChannel(chchanint){_//6send_gofunc(){ch<-1}()gofunc(){ch<-2}()gofunc(){ch<-3}()gofunc(){ch<-4}()gofunc(){ch<-5}()gofunc(){ch<-6}()_//8receive_gofunc(){<-ch}()gofunc(){<-ch}()gofunc(){<-ch}()gofunc(){<-ch}()gofunc(){<-ch}()gofunc(){<-ch}()gofunc(){<-ch}()gofunc(){<-ch}()时间。Sleep(time.Second*5)fmt.Println("stop")}调试阶段分析就是调试buffer通道上的代码。第一缓冲类型:当执行到CheckChannel时,已经初始化了一块内存。打开dehug界面查看数据结构:此时刚初始化,底层数组元素个数为0,发送索引和接收索引都指向数组索引0,发送中没有G和接收队列。发送一个元素然后stepover,执行第一个G,观察debug观察红框内容,看到底层数组0位置已经接收到第一个groutine的数据,底层sendx指针指向到数组索引1,表示再次发送的数据会从数组索引1接收,recvx为0,表示G接收数据时,会接收数组索引0处的数据。此时,结构:发送块继续stepover,直到发送数据结束,查看debug:可以看到因为没有receiver,底层数组已经满了,sendx和recvx的值都是0,表示如果有接收者就取索引0处的数据,如果有发送者则将数据复制到0处(如果0处的数据被取出)。另外查看sendq可以看到G队列已经积压了。这是因为底层数组已满。channel会创建一个sudog数据结构,获取G的指针,将G放入自己的等待队列中。此时,积压的G处于Gwaiting状态,既不在全局运行队列中,也不在某个P(调度器)的运行队列中,等待一个receiver接收数据,触发goready函数使G进入schedulableorGrunnablestate:可以看出在这个结构中有3个Gbacklogs,通过next连接下一个G。当然还有一个pre指针连接前面的G,第一个或者最后一个G的指针指向一个nil。此时结构:接收到第一个元素,继续step到第一个接收到的G,观察通道结构:可以看到索引0处的1已经接收到,因为有接收者,G在等待队列中被接收唤醒,进入schedulable或Grunnable状态,在scheduled执行结束后被释放。sendq队列中backlog的第二个G为队头,sendx和recvx指向索引1,通道在数组索引1处收发数据。查看sendq中的G队列:可以看到队首的groutine已经被释放,队列中只剩下两个G;第一批接收结束后,继续stepover,第三个接收G执行结束:可以看到groutines数组中的所有元素都变成了第一次积压在sendq中的G要发送的元素,并且由于都发送完了,所有积压的G都被释放,所有的索引指针都指向0。在数组的开头,有一个空闲位,继续步过,观察sendx和recvx的指针位置:可以看出,由于没有sender,所以sendx的指针指向索引0,recvx向后移动给剩余的数组元素赋值。此时G没有积压,接收者释放一个索引位。此时的数据结构:数组没有元素,继续stepover,直到第六个接收G:恢复原来的效果,没有元素,也没有积压的G。此时的数据结构如图:接收块继续step过去创建接收G,直到G创建完成,观察:recvq接收队列中有两个积压的G被阻塞,陷入Gwaiting状态.由于程序后面不会有sender,所以会一直阻塞到主协程退出。此时数据结构:调试后继续执行,主协程休眠五秒,退出,所有子协程退出。对于非缓冲通道,值直接从发送的G复制到接收的G。调试总结归根结底,通过通道传递消息是值的副本。有缓冲的通道先将发送端G的值拷贝到自己维护的数组中,再拷贝给接收端的G。非缓冲通道直接从发送栈中拷贝数据。接收堆栈空间。最后贴一张图:原创文章,转载请注明出处。
