GO Channel源码分析

GO Channel源码分析


本文档主要分析go channel源码,go 版本为1.15.4,源码路径GOROOT/src/runtime/chan.go

针对读写,select,for range进行源码分析,更倾向于看一下对应底层调用了什么方法。channel原理,go的切换与GMP的关联,放在另一篇文章再叙述。

Channel结构体
type hchan struct {
	qcount   uint           // 有效用户元素,在出队入队时改变
	dataqsiz uint           // buffer长度,初始化时赋值,不会改变
	buf      unsafe.Pointer // buffer数组的地址
	elemsize uint16         // 元素大小,结合dataqsiz可以计算出buf内存大小
	closed   uint32
	elemtype *_type         // 元素类型
	sendx    uint           // 
	recvx    uint           // receive index
	recvq    waitq          // 等待recv响应的对象列表
	sendq    waitq          // 等待send响应的对象列表

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

//双向队列
type waitq struct {
    first *sudog
    last  *sudog
}

c := make(chan int)

初始化channel对应的底层源码就是makechan,输入是一个chantype(元素类型)和size(槽位大小)

在这里插入图片描述

参数校验完后,会对hchan初始化。switch三种case表示三种初始化的类型:

zero buf

no buffer可以看做chan

non pointer element

channel元素不含指针的场景,会分配一个较大的内存空间。由于一起分配(L101,L102),所以内存是连续分配的。

pointer element

默认场景,channel元素带指针,单独分配内存

初始化elemsize/elemtype/dataqsiz

在这里插入图片描述

blocking Write & non blocking Write
//blocking,对应chansend的block参数为true
c <- v

//non blocking,对应chansend的block参数为false。
//没有写default,也没有receiver的话也会deadlock
select {
    case c<-v :{
        //...
    }
	default:
    	//...
}
c <- v

c为channel,v为写入的值

对一个channel的写操作,底层对应chansend方法,一般有2种情况

  • 写入成功,顺利正常返回
  • 写入失败
    • blocking channel:函数阻塞,goruntine切走
    • non blocking channel:return false

在这里插入图片描述

  1. 对一个non blocking,且没有缓冲区的channel写入,在没有receiver的情况下,会return false,但不会阻塞。由于,没有receiver,所以不需要lock,可以直接判断。

    //仅non blocking会在这里判断
    func nonBlockingChannel() {
    	c := make(chan int)
    	select {
    	case c <- 1 :{
    		fmt.Println("1")
    	}
    	default:
            //打印这里
    		fmt.Println("2")
    	}
    }
    

    如果有receiver,就不会有问题,见下面的第4点。除非是带有buffer的channel,见第5点。

  2. channel操作,都在互斥锁下完成,其中一个主要目的就是要锁receq和sendq

  3. 禁止往关闭的channel写数据,不然会panic

    func writeClosedChannel() {
        c := make(chan int)
        close(c)
        c <- 1
    }
    

在这里插入图片描述

  1. 有channel在等着收数据,就取出来往这个channel写

在这里插入图片描述

相比较第1点没有receiver,如果有receiver,full()就是false。取得一个receiver后,发送。

//non blocking channel
//先准备一个receiver,等到select写入的时候就不会return false
func nonBlockingChannelWithoutBufferWrite() {
	c := make(chan int)

	go func() {
		for {
			select {
			case <-c:
				{
					fmt.Println(time.Now().Unix(), "receive")
				}
			}
		}
	}()

	select {
	case c <- 1:
		{
			fmt.Println(time.Now().Unix(), "send")
		}
	}
}

//blocking
func blockingChannelWithoutBufferWrite() {
	c := make(chan int)

	go func() {
		select {
		case <-c:
			{
				fmt.Println("no deadlock")
			}
		}
	}()

	c <- 1
}
  1. buffer还有空间,就元素往里面写,递增索引

在这里插入图片描述

在有buffer的时候,写入数据返回true,不会导致deadlock。

//non blocking channel
func nonBlockingChannelWithBuffer() {
	c := make(chan int1)
	select {
	case c <- 1 :{
        //打印这里
		fmt.Println("1")
	}
	default:
		fmt.Println("2")
	}
}

//blocking channel
func blockingChannelWithBuffer() {
    c := make(chan int, 2)
	c <- 1
	fmt.Println("its available")
}

非pointer value的channel,会分配一个连续的内存空间,所以在写入数据的时候,可以通过指针位移的方式去写入。

  1. 非阻塞式超过buffer大小直接返回,select的时候block为true。如果是blocking channel会直接到第7步,切走goruntine。

    当写入数据长度超过buffer大小时,即dataqsiz,如果不是select的情况,会一直阻塞deadlock。

    //non blocking channel写入超过buffer长度的数据时,会执行default里面的内容
    func channelWithBufferWriteOver() {
    	c := make(chan int, 2)
    	for i := 0; ; i++ {
    		select {
    		case c <- i:
    			{
    				fmt.Println("send success: ", i)
    			}
    		default:
    			fmt.Println("buffer over")
    			return
    		}
    	}
    }
    

在这里插入图片描述

  1. goruntine切走,L252将goruntine入队,等待条件唤醒。L258,gopark将切走goruntine,让出CPU。代码块里面末尾’…’,就是等待唤醒的代码

    //blocking channel在写入超过buffer长度后会deadlock
    func channelWithBufferWriteOver() {
    	c := make(chan int, 2)
    	c <- 1
    	fmt.Println("1")
    	c <- 2
    	fmt.Println("2")
    	c <- 3
    	fmt.Println("3")
    }
    

在这里插入图片描述


blocking Read & non blocking Read
//non blocking read
select {
    case v := <-c:
		... foo
	default:
		... bar
}

select {
    case v,ok := <-c:
		... foo
	default:
		... bar
}
//blocking read
<- c
或
v,ok := <-c

<- c和v,ok:=<-c

读channel里面数据分为两种情况,就是是否有判断channel关闭

<-c对应的是chanrec1

在这里插入图片描述

v,ok :=<-c对应的是chanrecv2

在这里插入图片描述

两个调用的底层方法都是chanrecv,总共有三种返回结果

  • 如果是非阻塞模式(block为false),并且没有任何可用元素,返回selected=false,received=false,这样就不会进入select的case分支
  • 如果是阻塞式(block为true),在channel已经close的情况下,会返回selected=true,received=false,因为channel已经关闭,读不到数据了
  • 如果是阻塞模式,且channel还未关闭,返回selected=true,received=true,表示有读到数据

和写最大的区别就在于读closed的channel是不会panic,以及对recv队列入队,sendq队列出队

在这里插入图片描述

  1. non blocking channel先判断是否为空,直接返回false。和chansend不同的是,这里使用原子操作来保证。

    To prevent reordering, we use atomic loads for both checks, and rely on emptying and closing to happen in separate critical sections under the same lock. This assumption fails when closing an unbuffered channel with a blocked send, but that is an error condition anyway.

    为了防止重新排序,我们对这两种检查都使用原子加载,并依赖于在同一锁下的不同临界区中发生清空和关闭。当关闭一个发送阻塞的非缓冲通道时,这种假设就失败了,但无论如何,这是一个错误条件。

    //non blocking read with empty data in channel
    func nonBlockingChannelRead() {
    	c := make(chan int)
    
    	select {
    	case <-c:
    		fmt.Println("receive")
    	default:
    		fmt.Println("no receive")
    	}
    }
    
  2. 从一个closed的channel读,是不会panic的,这个和write不同,切记。最后返回的received为false,因此在读channel的时候,最后判断一下channel是否关闭。

在这里插入图片描述

func readClosedChannel() {
	c := make(chan int)
	close(c)
	<-c
	fmt.Println("channel close")

	_, ok := <-c
	if !ok {
		fmt.Println("channel no ok")
		return
	}

	fmt.Println("success")
}

在这里插入图片描述

  1. 有等待的sender,就从sendq中dequeue出来,读数据。有write但是没有read,会进到sendq里。

    //non blocking channel with buffer read
    func nonBlockingChannelWithBufferRead() {
    	c := make(chan int, 1)
    	c <- 1
    
    	select {
    	case v,_ := <-c:
    		{
    			fmt.Println(v)
    		}
    	default:
    		fmt.Println("no one send")
    	}
    }
    
    
    //blocking channel with buffer read
    func blockingChannelWithBufferRead() {
    	c := make(chan int)
    
    	go func() {
    		c <- 1
    	}()
    	fmt.Println("start hold")
    	time.Sleep(time.Second)
    	v := <-c
    	fmt.Println("read:", v)
    }
    
  2. non blocking或者带有缓冲的channel send完,然后有人来读

    func nonBlockingChannelReadDirect() {
    	c := make(chan int, 2)
    	c <- 1
    	c <- 2
    
    	for i := 0; ; i++ {
    		select {
    		case v := <-c:
    			{
    				fmt.Println("read success: ", v)
    			}
    		default:
    			fmt.Println("buffer empty")
    			return
    		}
    	}
    }
    

在这里插入图片描述

func blockingChannelWithBufferReadDirect() {
	c := make(chan int, 2)
	c <- 1
	c <- 2
	v := <-c
	fmt.Println("read 1 : ", v)
	v = <-c
	fmt.Println("read 2 : ", v)
}
  1. 没想到什么情况下会走过来,这里的情况,猜想是lock之后,某些情况导致,non blocking channel在read的时候qcount被别的channel读完,导致了第4点没执行到。

  2. goruntine切走,L571将goruntine入队,等待条件唤醒。L577,gopark将切走goruntine,让出CPU。代码块里面末尾’…’,就是等待唤醒的代码

    //blocking channel把buffer读完后,再读会deadlock
    func channelWithBufferReadOver() {
    	c := make(chan int, 2)
    	c <- 1
    	c <- 2
    	v := <-c
    	fmt.Println("read 1 : ", v)
    	v = <-c
    	fmt.Println("read 2 : ", v)
    	v = <-c
    	fmt.Println("read 3: ", v)
    }
    

在这里插入图片描述


select
结合c <- v

c为channel,v为写入的值

select {
    case c <- v :
     	//...
	default:
    	//...
}

转换为

if selectnbsend(c,v) {
    //...
} else {
    //...
}

对应底层源码selectnbsend

在这里插入图片描述

范例代码见上面c<-v第5点

结合v: =<-c
select {
    case v := <- c :
     	//...
	default:
    	//...
}

转换为

if selectnbrecv(&v,c) {
    //...
} else {
    //...
}

对应底层源码selectnbrecv

在这里插入图片描述

范例代码见上面<-c和v,ok:=<-c第3,4点

结合v,ok:=<-c
select {
    case v,ok := <- c :
     	//...
	default:
    	//...
}

转换为

if selectnbrecv2(&v,&ok,c) {
    //...
} else {
    //...
}

对应底层源码selectnbrecv2

在这里插入图片描述

范例代码见上面<-c和v,ok:=<-c第3,4点


channel range
for m:= range c{
    //...
}

转换为

for(;ok = chanrecv2(c,ep);) {
    //...	
}

对应底层源码chanrecv2

在这里插入图片描述

//for range也是一个blocking channel,没有write,就会deadlock
func forRangeChannel() {
	c := make(chan int, 2)

	c <- 1
	c <- 2
	for v := range c {
		fmt.Println("read :", v)
	}
}

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值