golang的引用类型的数据类型有三个chan、slice、map,虽然golang的函数的参数都是值传递,传递引用类型参数只会拷贝参数的变量,实际引用的地址仍然是一个,所以会改变函数外部的变量的值,产生变量逃逸到堆的现象。废话优点多了,今天看一眼golang的chan类型,chan类型是设计用来做goroutine通信的,类似unix的管道,如果了跨进程的通信还是用分布式来解决比较好,chan解决的是goroutine之间的通信。先看一下源码src/runtime/chan.go:hchan 32行,我的版本是1.13.5
type hchan struct {qcount uint // total data in the queue 剩余空间dataqsiz uint // size of the circular queue 队列长度 buf unsafe.Pointer // points to an array of dataqsiz elements 队列指针 elemsize uint16 //元素大小 closed uint32 //chan是否已经关闭 elemtype *_type // element type 元素类型 sendx uint // send index 写入新的元素存放位置 recvx uint // receive index 读取元素的位置 recvq waitq // list of recv waiters 等待读的goroutine队列 sendq waitq // list of send waiters 等待写的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 //互斥锁}
基本上是实现了一个环形队列,如果是缓冲队列环形队列作为缓冲区,队列的长度make(chan string, len)时由len指定,就是可以缓冲多少个元素,qcount标识还有多少个空闲的缓冲位置,buf指针指向队列的地址,sendx是新写入元素写入的队列的下标,recvx是下次读出数据的队列的下标。
chan通过两个队列和一个互斥锁实现不同goroutine的同步。首先如果chan的缓冲区满了,读写goroutine都会阻塞,会分别在recvq和sendq两个队列里挂起等待被唤醒;在有新的数据写入chan时recvq队列等待的读goroutine会被唤起,同样写队列会在chan缓冲有空位的时候唤起;无缓冲的chan的qcount=0,这时recvq和sendq肯定有一个队列是空的一个不是空的,因为没有位置区缓冲,只能在写入时候唤起读去读出来,同样写也只能在有读队列在等待的情况下写入,否则会panic。同一个chan只能允许一个goroutine读写是lock mutex互斥锁实现的。
chan结构体包含定义的元素的数据格式elemtype,只能存储指定类型的值;elemsize是元素的大小,用于找到buf中的位置。
初始化chan,make(chan string, len)len执行缓冲长度,string指定元素类型,buf大小由长度和元素大小共同决定;下面函数基本上就是创建hchan的数据结构和类型内存安全的判断
func makechan(t *chantype, size int) *hchan { elem := t.elem // compiler checks this but be safe. if elem.size >= 1<<16 { throw("makechan: invalid channel element type") } if hchanSize%maxAlign != 0 || elem.align > maxAlign { throw("makechan: bad alignment") } mem, overflow := math.MulUintptr(elem.size, uintptr(size)) if overflow || mem > maxAlloc-hchanSize || size < 0 { panic(plainError("makechan: size out of range")) } // Hchan does not contain pointers interesting for GC when elements stored in buf do not contain pointers. // buf points into the same allocation, elemtype is persistent. // SudoG's are referenced from their owning thread so they can't be collected. // TODO(dvyukov,rlh): Rethink when collector can move allocated objects. var c *hchan switch { case mem == 0: // Queue or element size is zero. c = (*hchan)(mallocgc(hchanSize, nil, true)) // Race detector uses this location for synchronization. c.buf = c.raceaddr() case elem.ptrdata == 0: // Elements do not contain pointers. // Allocate hchan and buf in one call. c = (*hchan)(mallocgc(hchanSize+mem, nil, true)) c.buf = add(unsafe.Pointer(c), hchanSize) default: // Elements contain pointers. c = new(hchan) c.buf = mallocgc(mem, elem, true) } c.elemsize = uint16(elem.size) c.elemtype = elem c.dataqsiz = uint(size) if debugChan { print("makechan: chan=", c, "; elemsize=", elem.size, "; elemalg=", elem.alg, "; dataqsiz=", size, "") } return c}
写chan就是先判断等待队列是否空,不空就从recvq取出一个goroutine把数据写入,唤醒这个goroutine;如果没有等待的读groutine,就写入缓冲区,缓冲区没有位置就把写入数据写入当前goroutine在sendq排队挂起等待被唤起。
读chan同样,先读缓冲区数据,缓冲区数据,判断sendq是否为空,不空则把goroutine唤起写入缓冲,读结束;没有数据可读则加入recvq队列挂起等待唤起。
关闭chan,会清理recvq和sendq的队列;sendq队列的goroutine会panic,recvq队列的goroutine写入的数据设置为空就是nil;所以关闭的chan继续写入会panic,但是还是能读关闭chan的buf数据。
常用使用demo:
单向队列
func main() { var c = make(chan int, 10) writeChan(c) readChan(c)}func readChan(flow
多路io监听由select实现
func addNumberToChan(chanName chan int) {for {chanName
select监听多路io就是多个chan,没有chan有数据会走default代码块,顺序是随机轮询的没有default语句的话select也会继续循环轮询监听
range循环从chan读数据,就是遍历buf数组,当chan没有数据会阻塞到goroutine
func chanRange(chanName chan int) { for e := range chanName { fmt.Printf("Get element from chan: %d", e) }}