go|channel源码分析

channel

先看一下源码中的说明

At least one of c.sendq and c.recvq is empty, except for the case of an unbuffered channel with a single goroutine blocked on it for both sending and receiving using a select statement, in which case the length of c.sendq and c.recvq is limited only by the size of the select statement.
For buffered channels, also:
c.qcount > 0 implies that c.recvq is empty.
c.qcount < c.dataqsiz implies that c.sendq is empty.

c.sendq和c.recvq至少有一个是empty;有一种情况例外:对于一个无缓冲的chan,在select语句中使用chan收发数据会被single gouroutine阻塞,[演示示例如下],此时c.sendq和c.recv.q的长度由select的size的决定;对于有缓冲的chan:
1.c.qcount>0,表明c.recvq为empty
2.c.qcount<c.dataqsiz表明sendq为empty

  • 对于"unbuffered chan",放在select语句中会被阻塞
    在这里插入图片描述
  • 放在一个goroutine中会产生"dead lock"
    在这里插入图片描述
  • 放在不同的协程中就不会产生dead lock
    在这里插入图片描述
  • 对于nil chan放在同一个协程中和不同的协程中都会被阻塞产生deadlock;放在select语句可以走default分支
  • nil chan只是声明而没有定义,没有为其分配内存;empty chan为其分配内存但是无缓冲

hchan

接下来看一下chan结构体"hchan"的定义

type hchan struct {
   
	qcount   uint           // buf中的元素个数
	dataqsiz uint           // buf的容量
	buf      unsafe.Pointer // 指向存储数据的底层数组
	elemsize uint16  //元素大小
	closed   uint32  //标识chan是否关闭
	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保护hchan中的所有字段,以及在此通道上阻塞的sudogs中的几个字段。
	lock mutex
}
type waitq struct {
   
	first *sudog 
	//sudog represents a g in a wait list, 
	//such as for sending/receiving on a channel.
	last  *sudog
}
//只列出了部分字段
type sudog struct {
   
	g *g

	next *sudog
	prev *sudog
	......
	......
	c        *hchan // channel
}

makechan

利用"make(chan int,1)“创建并初始化一个chan,会调用"makechan”

func makechan(t *chantype, size int) *hchan {
   
	//获取chan中存储元素的相关信息
	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")
	}
	//elem.size表示元素的大小,size表示元素的个数
	mem, overflow := math.MulUintptr(elem.size, uintptr(size))
	if overflow || mem > maxAlloc-hchanSize || size < 0 {
   
		panic(plainError("makechan: size out of range"))
	}

	
	var c *hchan
	switch {
   
	case mem == 0:
		//元素大小为0或者元素个数为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:
		//元素不包含指针类型的数据,一次性分配hchan和buf
		// 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:
		//元素含有指针,需要先为hchan分配内存,然后再为buf分配内存,这样做是为了方便gc回收
		// Elements contain pointers.
		c = new(hchan)
		c.buf = mallocgc(mem, elem, true)
	}

	c.elemsize = uint16(elem.size)
	c.elemtype = elem
	c.dataqsiz = uint(size)
	lockInit(&c.lock, lockRankHchan)

	if debugChan {
   
		print("makechan: chan=", c, "; elemsize=", elem.size, "; dataqsiz=", size, "\n")
	}
	return c
}

总结一下分配的方式:

  • 对于无缓冲的chan [size==0]或者chan中存储的元素大小为0[elemsize==0]只分配hchan结构体大小的内存
  • 不含有指针类型的数据,一次性分配hchan+bufsize大小的内存
  • 含有指针类型的数据,先为hchan结构体分配内存,然后再为buf分配内存

chansend

接下来看一下,向chan中发送数据的流程

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
   
	if c == nil {
   
		if !block {
   
			return false
		}
		//当前的协程因向nilchan发送send而被挂起阻塞
		gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
		throw("unreachable")
	}

	if debugChan {
   
		print("chansend: chan=", c, "\n")
	}

	if raceenabled {
   
		racereadpc(c.raceaddr(), callerpc, abi.FuncPCABIInternal(chansend))
	}

	/*
	fast path:
	在不获取锁的情况下,检查失败的非阻塞操作
	full(c)为true的情况:
	1.无缓冲但是没有等待接收的reciver
	2.有缓冲但是缓冲通道满了
	*/
	if !block && c.closed == 0 && full(c) {
   
		return false
	}
	
	var t0 int64
	if blockprofilerate > 0 {
   
		t0 = cputicks()
	}
	//上锁
	lock(&c.lock)

	if c.closed != 0 {
   
	//chan已经被关闭解锁,并报panic
		unlock(&c.lock)
		panic(plainError("send on closed channel"))
	}

	//c.recvq.dequeue获取recvq的第一个sudog
	
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值