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)     }}