1. channel 概述

channel 是golang中的一种数据结构,主要用来实现golang协程之间的通信,channel的设计也是提现了golang对于进程间消息传递的观点:“用通信实现共享内存,而不是使用共享内存实现通信”。

2. channel 数据结构
hchan
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
	elemtype *_type // element type
	sendx    uint   // send index
	recvx    uint   // receive index
	recvq    waitq  // list of recv waiters
	sendq    waitq  // list of send waiters
	lock mutex
}

2.1. 各个字段的含义以及作用

bufsendxrecvxqcountdataqsizeelemsizeclosedelemtypesendxrecvxrecvqsendqlock
elemsizeelemsize

recvq与sendq的结构:waitq

type waitq struct {    
	first *sudog    
	last  *sudog
}

type sudog struct {    
	// Current goroutine    
	g *g    
	// isSelect indicates g is participating in a select, so    
	// g.selectDone must be CAS'd to win the wake-up race.    
	isSelect bool    
	next     *sudog    
	prev     *sudog    
	elem     unsafe.Pointer // data element (may point to stack)    
	// The following fields are never accessed concurrently.    
	// For channels, waitlink is only accessed by g.    
	// For semaphores, all fields (including the ones above)    
	// are only accessed when holding a semaRoot lock.    
	acquiretime int64    
	releasetime int64    
	ticket      uint32    
	parent      *sudog // semaRoot binary tree    
	waitlink    *sudog // g.waiting list or semaRoot    
	waittail    *sudog // semaRoot   
	c           *hchan // channel
}

不是很懂这些字段和作用…

2.2. channel工作过程:读与写

2.3. 操作 channel 后:成功、阻塞或者panic

truefalsetrue

2.4. range 与 select

rangerange
c := make(chan int, 10)
go func(){
	for i:=0;i<5;i++{
		c<-i
	}
	close(c)
}
for v,ok := range c {
	if !ok{
		fmt.Println(v,ok)
		break
	}
	fmt.Println(v,ok)
}
// 输出
0 true
1 true
2 true
3 true
4 true
0 false
select
contextContextDone()DoneWithCancel()ContextCancalFunccancelDoneDoneDoneDone
func main() {
	gen := func(ctx context.Context) <-chan int {
		dst := make(chan int)  // 无缓冲通道
		n := 1
		go func() {
			for {
				select {
				case <-ctx.Done():
					return
				case dst <- n:
					n++
				}
			}
		}()
		return dst
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	for v := range gen(ctx) {
		fmt.Println(v)
		if v == 5 {
			break
		}
	}
}
// 输出
1
2
3
4
5

2.5. channel 缓冲:有与无

有缓冲
有缓冲通道可以让goroutine之间的信息传递更加高效,减少不必要的阻塞,通过 2.3 小节我们知道

  • 读一个非空的有缓冲通道不会阻塞
  • 写一个非满的有缓冲通道不会阻塞
c := make(chan int, 10)  // 定义一个最多可以缓存10个元素的带缓冲通道

无缓冲
无缓冲通常用来同步两个goroutine,对无缓冲通道的所有读(写)操作都将阻塞直到有写(读)操作发生

c := make(chan int)  // 定义一个不带缓冲的通道