不同于传统的多线程并发模型,使用共享内存实现线程间通信。golang的哲学是协程(goroutines)之间通过channel进行通信,实现数据共享:不通过共享内存进行通信;相反,通过通信共享内存。这种方法的优点是通过提供原子通信原语,它避免了竞争条件下的复杂锁定机制。一个channel可以看做一个FIFO队列。读取和写入FIFO队列是一个没有锁定的原子操作。通道的操作行为结果总结如下:对应类型零值阻塞或成功读取数据读取关闭的通道时,始终可以读取到对应类型的零值。为了区别于读取非空未关闭通道的行为,可以使用两个接收值://okisfalsewhenchiclosedv,ok:=<-chgolang中大部分类型都是值类型(只有slice/channel/map是引用类型),当读写类型为值类型的通道时,如果元素大小比较大,应该改用指针,避免频繁的内存拷贝开销。内部实现如图所示。在channel的内部实现中(具体定义在$GOROOT/src/runtime/chan.go中),维护了三个队列:readwaiting协程队列recvq,维护了读这个channel时阻塞的队列coroutinelistwritewaitingcoroutinequeuesendq,维护写本通道阻塞的协程listbuffer数据队列buf,用ringqueue实现。对于没有缓冲的channel,当coroutineattempt从未关闭时,queuesize为0img在channel中读取数据时,内部操作如下:1.当buf不为空时,此时recvq一定为空,buf弹出一个元素给读协程,读协程拿到数据后继续执行。此时如果sendq不为空,则sendq弹出一个write协程进入running状态,将要写入的数据放入队列buf中。这时候读操作<-ch没有被阻塞;2.当buf为空但sendq不为空(无缓冲通道)时,则sendq弹出一个写协程进入运行状态,将要写入的数据直接传递给读协程,读协程继续执行。这时候读操作<-ch没有被阻塞;3.当buf为空且sendq也为空时,读协程进入队列recvq,进入阻塞状态。当后面其他协程向通道写入数据时,读协程会重新进入运行状态。这时读操作<-ch块。同样,当协程尝试向未关闭的通道写入数据时,内部操作如下:当队列recvq不为空时,此时队列buf一定为空,从recvq弹出一个读协程接收要写入的数据。此时读协程结束阻塞,进入运行状态,写协程继续执行。此时写操作ch<-并没有被阻塞;当队列recvq为空但buf未满时,此时sendq必须为空,并将write协程要写入的数据放入buf中,然后继续执行。此时写操作ch<-并没有被阻塞;当队列recvq为空,buf为满时,将协程写入队列sendq,进入阻塞状态。当后续其他协程从通道读取数据时,写协程会重新进入运行状态,此时写操作ch<-blocks。当非nil通道关闭时,内部操作如下:当队列recvq不为空时,此时buf必须为空,recvq中的所有协程都会收到对应类型的零值,然后结束阻塞状态;当队列sendq不为空时,此时buf一定是满的,sendq中的所有协程都会panic,buf中的数据仍会保留,直到被其他协程读取。使用场景除了协程间传递数据的一般用法外,本节列举了一些通道的特殊使用场景。futures/promises虽然golang没有直接提供future/promise模型的操作原语,但是通过goroutine和channel可以实现类似的功能:packagemainimport("io/ioutil""log""net/http")//RequestFuture,httprequestpromise.funcRequestFuture(urlstring)<-chan[]byte{c:=make(chan[]byte,1)gofunc(){varbody[]bytedeferfunc(){c<-body}()res,err:=http.Get(url)iferr!=nil{return}deferres.Body.Close()body,_=ioutil.ReadAll(res.Body)}()returnc}funcmain(){future:=RequestFuture("https://api.github.com/users/octocat/orgs")body:=<-futurelog.Printf("reponselength:%d",len(body))}POSIX接口线程中的条件变量(conditionvariable)类型通知其他线程一个eventoccurs通道的条件变量,通道的特性也可以作为协程间同步的条件变量。因为通道只是用于通知,所以通道中具体的数据类型和值并不重要。在这种情况下,通常使用strct{}作为通道的类型。类似于pthread_cond_signal()的一对一通知函数,用于在一个协程中通知另一个协程事件:packagemainimport("fmt""time")funcmain(){ch:=make(chanstruct{})nums:=make([]int,100)gofunc(){time.Sleep(time.Second)fori:=0;i
