go源码阅读——chan.go

go 专栏收录该内容
7 篇文章 0 订阅

【博文目录>>>】 【项目地址>>>】

chan.go是go语言通道实现,通道结构的定义,接收和发送的操作都此文件中实现。

通道的结构

hchan是通道表示的基本结构,其内容表示如下:
一些特殊情况

  • 当dataqsiz=0时:说明这是一个无缓冲对列
  • 当dataqsiz>0时,说明是一个缓冲对列
type hchan struct {
    qcount   uint               // 队列中的数据总数
    dataqsiz uint               // 循环队列的大小
    buf      unsafe.Pointer     // 指向dataqsiz数组中的一个元素
    elemsize uint16
    closed   uint32             // 非0表示通道已经关闭
    elemtype *_type             // 元素类型
    sendx    uint               // 发送索引
    recvx    uint               // 接收索引
    recvq    waitq              // 所有等待的接收者
    sendq    waitq              // 所有等待的发送者

    // 锁保护hchan中的所有字段,以及此通道上阻止的sudogs中的多个字段。
    // 保持该锁状态时,请勿更改另一个G的状态(特别是不要准备好G),因为这会因堆栈收缩而死锁。
    // Question: sudogs?参见runtime/runtime2.go中的sudog结构
    lock mutex
}

通道的发送和接收

通道的发送和接收都是使用了一个名为waitq的等待对例,每个接收者和发送者都是一个sudog结构,此构在runtiem/runtime2.go文件中定义,在之后的源码分析中会仔细说明。

type waitq struct {
	first *sudog    // 队列头
	last  *sudog    // 队列尾
}

通道的对齐方法式

从文件的源码中可以知道,在内存中通道是以8字节的方式进行对齐的,内存分配不是8个字节会自动对对齐

const (
	maxAlign  = 8 // 8字节对齐
	hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1))
	debugChan = false
)

通道的使用

通道的创建的方法

在源码中通道的创建有三个实现方法

  • func reflect_makechan(t *chantype, size int) *hchan {…}
  • func makechan64(t *chantype, size int64) *hchan {…}
  • func makechan(t *chantype, size int) *hchan {…}
    其本质是调用最后一个通道的创建方法。

通道的创建过程

方法func makechan(t *chantype, size int) *hchan {...}说明了通道的整个创建过程。

/**
 * 创建通道
 * @param t 通道类型指针
 * @param size 通道大小,0表示无缓冲通道
 * @return
 **/
func makechan(t *chantype, size int) *hchan {...}

通道创建要通过以过几个步骤。

  • 1、检查通道元素类型的size,如果elem.size >= 1<<16,则创建失败。
  • 2、检查通道和元素的对齐,如果hchanSize%maxAlign != 0 || elem.align > maxAlign,则通道创建失败
  • 3、计算通道需要分配的内存,如果overflow || mem > maxAlloc-hchanSize || size < 0,则通道创建失败
    • 3.1、overflow:表示内存内存计算有溢出
    • 3.2、mem > maxAlloc-hchanSize:表示内存分配置超出了限制
    • 3.3、size < 0:表示通道缓冲为负数
  • 4、根据不同条件进行内存分配
    • 4.1、mem == 0:队列或者元素大小为0,这是表示元素或者队列大小为0,直接分配hchanSize大小的内存,缓冲地址向自身
    • 4.2、elem.ptrdata == 0:元素不包含指针,直接分配hchanSize+mem大小的内存,并且缓冲地址向自身+hchanSize
    • 4.3、其他情况(元素包含指针):使用new的方法创建一个hchan,分配mem大小的内存,并且缓冲地址向内存分配的地址
  • 5、最后设置通道结构的其他值

向通道中发送数据

向通道中发送数据的方法一共有四个,如下所示

  • func chansend1(c *hchan, elem unsafe.Pointer) {…}
  • func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {…}
  • func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {…}
  • func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {…}
    chansend1方法是go编译代码中c <- x的入口点,即当我们编写代码 c <- x时,就是调用此方法。chansend1方法本质还是调用chansend方法进行处理
    这四个方法的调用关系:chansend1 -> chansend -> send -> sendDirect

chansend方法

chansend方法的执行代表了事个数据的发送过程,方法签名如下:

/**
  * 通用单通道发送/接收
  * 如果block不为nil,则协议将不会休眠,但如果无法完成则返回。
  *
  * 当涉及休眠的通道已关闭时,可以使用g.param == nil唤醒休眠。
  * 最容易循环并重新运行该操作; 我们将看到它现已关闭。
  * @param c 通道对象
  * @param ep 元素指针
  * @param block 是否阻塞
  * @param callerpc 调用者指针
  * @return bool true:表示发送成功
  **/
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {...}

通道数据发送的处理过程:

  • 1、如果通道为空
    • 1.1、通道是非阻塞的,则返回false,表示发送失败,方法结束
    • 1.2、通道是阻塞的,则会阻塞当前goroutine,同时会执行一个throw方法,方法结束
  • 2、检查通道和数据状态,2.1,2.2,2.3同时满足时发关失败,方法结束
    • 2.1、!block:通道非阻塞
    • 2.2、c.closed == 0:通道未关闭
    • 2.3、((c.dataqsiz == 0 && c.recvq.first == nil) || (c.dataqsiz > 0 && c.qcount == c.dataqsiz))
      • 2.3.1、(c.dataqsiz == 0 && c.recvq.first == nil):通道中没有数据,并且没有接收者
      • 2.3.2、(c.dataqsiz > 0 && c.qcount == c.dataqsiz):通道中有数据,并且已经满了
  • 3、对通道进行锁定
  • 4、判断通道是否已经关闭,如果已经关闭,就解锁通道,并且抛出panic。方法结束
  • 5、在接收者队列中找出一个最先入队的接收者,如果有,就调用send方法进行发送,返回true,方法结束
  • 6、如果没有找到接收者,并且c.qcount < c.dataqsiz,即通道的发送缓冲区未满,将要发送的数据拷贝到通道缓冲区,更新相关的计数据信息,并释放锁,返回true,方法结束
  • 7、没有找到接收都,并且没有缓冲可用,非阻塞方式,则解锁通道,返回false,发送失败
  • 8、没有找到接收者,并且没有缓冲可用,阻塞方式。获取gp(g)和mysg(sudog),并且将发送数据挂到mysg上,将mysg加到发送队列。调用gopark访求将当前goroutine阻塞,直到被恢复。
  • 9、在恢复后我们还要将发送数据保活,以确保数据正确被接收者复制出去了。
  • 10、检查goroutine状态,如果mysg != gp.waiting说明被破坏了,执行throw,方法结束
  • 11、如果gp.param == nil,说明唤醒有问题,
    • 11.1、如果通道未关闭,则说明是伪唤醒,执行throw方法结束
    • 11.2、如果通道关闭,则panic,在关闭的通道中进行了发送消息。
  • 12、最后是清理数据,并且释放mysg

其他相关方法

/**
 * 编译器实现,将goroutine的select receive(v = <-c)语句转成对应的方法执行
 * select {
 * case v = <-c:
 * 	... foo
 * default:
 * 	... bar
 * }
 * 
 * => (实际对应)
 * 
 * if selectnbrecv(&v, c) {
 * 	... foo
 * } else {
 * 	... bar
 * }
 */
func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {...}

从通道中接收数据

chan.go文件中一共有五个方法实现用于接收通道内容,他们分别是:

  • func chanrecv1(c *hchan, elem unsafe.Pointer) {…},
  • func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {…}
  • func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {…}
  • func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {…}
  • func recvDirect(t *_type, sg *sudog, dst unsafe.Pointer) {…}
    其调用链 (chanrecv1|chanrecv2) -> chanrecv -> recv -> recvDirect
    其中chanrecv1方法是go代码<-c的入口点

chanrecv方法

chanrecv方法实现的通道接收的主要功能,其方法签名:

/**
 * 通用单通道发送/接收
 * 如果block不为nil,则协议将不会休眠,但如果无法完成则返回。
 *
 * 当涉及休眠的通道已关闭时,可以使用g.param == nil唤醒休眠。
 * 最容易循环并重新运行该操作; 我们将看到它现已关闭。
 * @param c 通道对象
 * @param ep 元素指针
 * @param block 是否阻塞
 * @param callerpc 调用者指针
 * @return bool true:表示发送成功
 **/
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {...}

通道数据接收的处理过程如下:

  • 1、如通道接收通道为nil
    • 1.1、如果接收是非阻塞的,则返回false,方法结束
    • 1.2、如果接收是阻塞的,则阻塞goroutine,即此goroutine会永久阻塞
  • 2、当2.1,2.2,2.3同时满足时,则返回false,方法结束
    • 2.1、!block == true,即非阻塞
    • 2.2、c.closed == 0,通道未关闭
    • 2.3、2.3.1,2.3.2两个满足其中一个
      • 2.3.1、c.dataqsiz == 0 && c.sendq.first == nil,通道中没有数据,并且没有发送者
      • 2.3.2、c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0,通道中有空间,但没有数据
  • 3、加锁
  • 4、如果通道已经关闭,并且没有数据,则解锁,进行内存清理,返回(true, false),方法结束
  • 5、从发送队列中取队头发送者,如果不为空,就用发送者发送的数据,返回(true, true),方法结束
  • 6、没有发送者,数据队列不为空(c.qcount > 0),直接从数据队列中接收数据,并且更新队列计数和接收索引指针,然后复制数据,清理内存,释放锁,最后返回(true, true),方法结束
  • 7、没有发送者,并且队列为空,并且是非阻塞状态,则释放锁,返回(false, false),方法结束
  • 8、没有找到发送都,并且没有缓冲可用,阻塞方式。获取gp(g)和mysg(sudog),并且将接收数据指针挂到mysg上,将mysg加到接收队列。调用gopark访求将当前goroutine阻塞,直到被恢复。
  • 9、在恢复后我们还是判断是否是意外唤醒,如果是,就panic,方法结束
  • 10、进行清理工作,释放sudog,返回(true, !closed)

recv方法

通道另一个核心方法就是recv,其方法签名如下:

/**
 * recv在完整通道c上处理接收操作。
 * 有2个部分:
 *      1)将发送方sg发送的值放入通道中,并唤醒发送方以继续进行。
 *      2)接收方接收到的值(当前G)被写入ep。
 * 对于同步通道,两个值相同。
 * 对于异步通道,接收者从通道缓冲区获取数据,而发送者的数据放入通道缓冲区。
 * 频道c必须已满且已锁定。 recv用unlockf解锁c。
 * sg必须已经从c中出队。
 * 非nil必须指向堆或调用者的堆栈。
 * @param c 通道对象针对
 * @param sg sudog指针
 * @param ep 用户接收元素的指针
 * @param unlockf 解锁函数
 * @param skip
 * @return
 **/
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {...}

recv接收数据的过程

  • 1、对于无缓冲通道(c.dataqsiz == 0),当数据指针不空的时候,直接拷贝发送指针所指的数据
  • 2、对于缓冲通道,将数据写入到缓冲中,并且更新缓冲计数,接收索引,发送索引
  • 3、进行goroutine相关处理,释放锁,并且将当前goroutine置于等待

其他相关方法

/**
 * 编译器实现,将goroutine的select receive(v = <-c)语句转成对应的方法执行
 * select {
 * case v = <-c:
 * 	... foo
 * default:
 * 	... bar
 * }
 * 
 * => (实际对应)
 * 
 * if selectnbrecv(&v, c) {
 * 	... foo
 * } else {
 * 	... bar
 * }
 **/
func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected bool) {...}

/**
 * 编译器实现,将goroutine的select receive(case v, ok = <-c:)语句转成对应的方法执行
 * select {
 * case v, ok = <-c:
 *  ... foo
 * default:
 *  ... bar
 * }
 *
 * => (实际对应)
 *
 * if c != nil && selectnbrecv2(&v, &ok, c) {
 *  ... foo
 * } else {
 *  ... bar
 * }
 **/
func selectnbrecv2(elem unsafe.Pointer, received *bool, c *hchan) (selected bool) {...}

通道关闭

chan.go中只有一个关闭通道的方法

  • func closechan(c *hchan) {…}
    通过的关闭过程
  • 1、如果通道为nil,则painc,方法结束
  • 2、锁定通道
  • 3、如果通道已经关闭了,解锁通道,并且panic,方法结束,即通道只能关闭一次
  • 4、标记通道已经关闭
  • 5、释放所有的接收者,将接收者入gList队列
  • 6、释放所有的发送者,将发送者入gList队列
  • 7、释放锁
  • 8、将gLsit中的元素都标记成进入goready状态

源码

package runtime

// This file contains the implementation of Go channels.

// Invariants:
//  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.
/**
 * 此文件包含Go渠道的实现。此包中所使用的类型大部分定义在:runtime/type.go文件中
 *
 * 不变量:
 * c.sendq和c.recvq中的至少一个为空,但在无缓冲通道上阻塞了单个goroutine以便使用select语句发送和接收的情况除外,在这种情况下,
 * c.sendq的长度 而c.recvq仅受select语句的大小限制。
 *
 * 对于缓冲通道,还:
 * c.qcount> 0表示c.recvq为空。
 * c.qcount <c.dataqsiz表示c.sendq为空。
 */
import (
	"runtime/internal/atomic"
	"runtime/internal/math"
	"unsafe"
)

const (
	maxAlign  = 8 // 8字节对齐
	hchanSize = unsafe.Sizeof(hchan{}) + uintptr(-int(unsafe.Sizeof(hchan{}))&(maxAlign-1))
	debugChan = false
)

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 // 指向dataqsiz数组中的一个元素
	elemsize uint16
	closed   uint32  // 非0表示通道已经关闭
	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 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.
	// 锁保护hchan中的所有字段,以及此通道上阻止的sudogs中的多个字段。
    // 保持该锁状态时,请勿更改另一个G的状态(特别是不要准备好G),因为这会因堆栈收缩而死锁。
    // Question: sudogs?参见runtime/runtime2.go中的sudog结构
	lock mutex
}

/**
 * 等待队列数据结棍
 */
type waitq struct {
	first *sudog    // 队列头
	last  *sudog    // 队列尾
}

//go:linkname reflect_makechan reflect.makechan
/**
 * 通过反射创建通道,
 * @param
 * @return
 **/
func reflect_makechan(t *chantype, size int) *hchan {
	return makechan(t, size) // 在reflect/value.go/makechan方法中实现
}

/**
 * 创建通道
 * @param t 通道类型
 * @param size 看上去是支持64位int,本质上只支持int类型
 * @return
 **/
func makechan64(t *chantype, size int64) *hchan {
	if int64(int(size)) != size { // 说明有溢出
		panic(plainError("makechan: size out of range"))
	}

	return makechan(t, int(size))
}

/**
 * 创建通道
 * @param t 通道类型指针
 * @param size 通道大小,0表示无缓冲通道
 * @return
 **/
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")
	}
	// 非8字节对齐
	if hchanSize%maxAlign != 0 || elem.align > maxAlign {
		throw("makechan: bad alignment")
	}

    // MulUintptr返回a * b以及乘法是否溢出。 在受支持的平台上,这是编译器固有的功能。
	mem, overflow := math.MulUintptr(elem.size, uintptr(size))
	// 发生溢出,或者分配内存超限制,或者size<0
	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.
	// 当存储在buf中的元素不包含指针时,Hchan不包含GC感兴趣的指针。 buf指向相同的分配,
	// elemtype是持久的。 SudoG是从它们自己的线程中引用的,因此无法收集它们。
    // TODO(dvyukov,rlh):重新考虑何时收集器可以移动分配的对象。
	var c *hchan
	switch {
	case mem == 0: // 不需要分配置内存空间
		// Queue or element size is zero.
	    // 队列或元素大小为零。
	    // mallocgc分配一个大小为size字节的对象。 小对象是从每个P缓存的空闲列表中分配的。
	    // 大对象(> 32 kB)直接从堆中分配。
		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.
		// 元素不包含指针。 在一次调用中分配hchan和buf。
		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, "; dataqsiz=", size, "\n")
	}
	return c
}

// chanbuf(c, i) is pointer to the i'th slot in the buffer.
/**
 * chanbuf(c, i) 返回指向缓冲区中第i个槽值的指针。
 * @param c 通道对象
 * @return i 第i个槽位
 **/
func chanbuf(c *hchan, i uint) unsafe.Pointer {
	return add(c.buf, uintptr(i)*uintptr(c.elemsize))
}

// entry point for c <- x from compiled code
//go:nosplit
/**
 * 编译代码中c <- x的入口点,即当我们编写代码 c <- x时,就是调用此方法
 * @param c 通道对象
 * @param elem 需要发送的元素
 * @return
 **/
func chansend1(c *hchan, elem unsafe.Pointer) {
	chansend(c, elem, true, getcallerpc())
}

/*
 * generic single channel send/recv
 * If block is not nil,
 * then the protocol will not
 * sleep but return if it could
 * not complete.
 *
 * sleep can wake up with g.param == nil
 * when a channel involved in the sleep has
 * been closed.  it is easiest to loop and re-run
 * the operation; we'll see that it's now closed.
 */
 /**
  * 通用单通道发送/接收
  * 如果block不为nil,则协议将不会休眠,但如果无法完成则返回。
  *
  * 当涉及休眠的通道已关闭时,可以使用g.param == nil唤醒休眠。
  * 最容易循环并重新运行该操作; 我们将看到它现已关闭。
  * @param c 通道对象
  * @param ep 元素指针
  * @param block 是否阻塞
  * @param callerpc 调用者指针
  * @return bool true:表示发送成功
  **/
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
	if c == nil { // 通道已为空
		if !block { // 非阻塞
			return false
		}
		// 将当前goroutine置于等待状态并调用unlockf。 如果unlockf返回false,则继续执行goroutine程序。
		// unlockf一定不能访问此G的堆栈,因为它可能在调用gopark和调用unlockf之间移动。
        // waitReason参数说明了goroutine已停止的原因。
        // 它显示在堆栈跟踪和堆转储中。
        // waitReason应具有唯一性和描述性。
        // 不要重复使用waitReason,请添加新的waitReason。
        // 更详细的说明参见:runtime/proc.go和runtime/runtime2.go
		gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
		throw("unreachable")
	}

	if debugChan { // 当前已经为false
		print("chansend: chan=", c, "\n")
	}

	if raceenabled { // 当前已经为false
		racereadpc(c.raceaddr(), callerpc, funcPC(chansend))
	}

	// Fast path: check for failed non-blocking operation without acquiring the lock.
	//
	// After observing that the channel is not closed, we observe that the channel is
	// not ready for sending. Each of these observations is a single word-sized read
	// (first c.closed and second c.recvq.first or c.qcount depending on kind of channel).
	// Because a closed channel cannot transition from 'ready for sending' to
	// 'not ready for sending', even if the channel is closed between the two observations,
	// they imply a moment between the two when the channel was both not yet closed
	// and not ready for sending. We behave as if we observed the channel at that moment,
	// and report that the send cannot proceed.
	//
	// It is okay if the reads are reordered here: if we observe that the channel is not
	// ready for sending and then observe that it is not closed, that implies that the
	// channel wasn't closed during the first observation.
	// 快速路径:在没有获取锁的情况下检查失败的非阻塞操作。
    //
    // 观察到通道未关闭后,我们观察到该通道尚未准备好发送。 这些观察中的每一个都是单个字(word)大小的读取
    // (根据通道的类型,取第一个c.closed和第二个c.recvq.first或c.qcount)。
    // 因为关闭的通道无法从“准备发送”转换为“未准备发送”,所以即使通道在两个观测值之间处于关闭状态,
    // 它们也隐含着两者之间的一个时刻,即通道既未关闭又未关闭准备发送。 我们的行为就好像我们当时在观察该通道,
    // 并报告发送无法继续进行。
    //
    // 如果在此处对读取进行了重新排序,也是可以的:如果我们观察到该通道尚未准备好发送,然后观察到它没有关闭,
    // 则意味着该通道在第一次观察期间没有关闭。
    // !block:非阻塞状态
    // c.closed == 0:通道未关闭
    // (c.dataqsiz == 0 && c.recvq.first == nil):通道中没有数据,并且没有接收者
    // (c.dataqsiz > 0 && c.qcount == c.dataqsiz):通道中有数据,并且已经满了
	if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
		(c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
		return false
	}

	var t0 int64
	if blockprofilerate > 0 {
		t0 = cputicks()
	}

    // 加锁
	lock(&c.lock)

	if c.closed != 0 { // 通道已经关闭,解锁,抛出panic
		unlock(&c.lock)
		panic(plainError("send on closed channel"))
	}

	if sg := c.recvq.dequeue(); sg != nil {
		// Found a waiting receiver. We pass the value we want to send
		// directly to the receiver, bypassing the channel buffer (if any).
		// 找到了等待的接收者。 我们绕过通道缓冲区(如果有)将要发送的值直接发送给接收器。
		send(c, sg, ep, func() { unlock(&c.lock) }, 3)
		return true
	}

    // 没有找到接收者,并且队列元素未填满通道的循环队列
	if c.qcount < c.dataqsiz {
		// Space is available in the channel buffer. Enqueue the element to send.
		// 通道缓冲区中有可用空间。 使要发送的元素入队。
		qp := chanbuf(c, c.sendx)
		if raceenabled { // 此值已经为false
			raceacquire(qp)
			racerelease(qp)
		}
		// 执行内存移动
		typedmemmove(c.elemtype, qp, ep)
		c.sendx++ // 指向下一个可以发送数据的空位
		if c.sendx == c.dataqsiz { // 已经达到了末尾
			c.sendx = 0 // 重新指向头部
		}
		c.qcount++ // 通道总的数据加1
		unlock(&c.lock) // 解锁
		return true // 说明发送成功
	}

    // 非阻塞,因为找不到接收者,所以失败
	if !block {
		unlock(&c.lock)
		return false
	}

	// Block on the channel. Some receiver will complete our operation for us.
    // 在通道上阻塞。一些接收器将为我们完成操作。
    // getg将返回指向当前g的指针。获取suodg
	gp := getg()
	mysg := acquireSudog()
	mysg.releasetime = 0
	if t0 != 0 {
		mysg.releasetime = -1
	}
	// No stack splits between assigning elem and enqueuing mysg
	// on gp.waiting where copystack can find it.
	// 在分配elem和将mysg入队到gp.waitcopy可以找到它的地方之间没有堆栈拆分。
	mysg.elem = ep
	mysg.waitlink = nil
	mysg.g = gp
	mysg.isSelect = false
	mysg.c = c
	gp.waiting = mysg
	gp.param = nil
	c.sendq.enqueue(mysg)
	gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
	// Ensure the value being sent is kept alive until the
	// receiver copies it out. The sudog has a pointer to the
	// stack object, but sudogs aren't considered as roots of the
	// stack tracer.
	// 确保发送的值保持活动状态,直到接收者将其复制出来。
	// sudog具有指向堆栈对象的指针,但是sudog不被视为堆栈跟踪器的根。
	KeepAlive(ep)

	// someone woke us up.
	// 有人把我们唤醒了
	if mysg != gp.waiting {
		throw("G waiting list is corrupted")
	}
	gp.waiting = nil
	gp.activeStackChans = false
	if gp.param == nil {
		if c.closed == 0 {
			throw("chansend: spurious wakeup")
		}
		panic(plainError("send on closed channel"))
	}
	gp.param = nil
	if mysg.releasetime > 0 {
		blockevent(mysg.releasetime-t0, 2)
	}
	mysg.c = nil
	releaseSudog(mysg) // 释放sudog
	return true
}

// send processes a send operation on an empty channel c.
// The value ep sent by the sender is copied to the receiver sg.
// The receiver is then woken up to go on its merry way.
// Channel c must be empty and locked.  send unlocks c with unlockf.
// sg must already be dequeued from c.
// ep must be non-nil and point to the heap or the caller's stack.
/**
 * send在空通道c上执行发送操作。
 * 将发送方发送的ep值复制到接收方sg。
 * 然后将接收器唤醒,继续前进。
 * 频道c必须为空且已锁定。 使用unlockf发送解锁通道c。
 * sg必须已经从c中出队。
 * ep必须为非nil,并指向堆或调用者的堆栈。
 * @param c 通道对象
 * @param sg
 * @param ep 元素指针
 * @param unlockf 角锁方法
 * @param skip
 * @return
 **/
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
	if raceenabled { // 此值已经为false
		if c.dataqsiz == 0 {
			racesync(c, sg)
		} else {
			// Pretend we go through the buffer, even though
			// we copy directly. Note that we need to increment
			// the head/tail locations only when raceenabled.
			qp := chanbuf(c, c.recvx)
			raceacquire(qp)
			racerelease(qp)
			raceacquireg(sg.g, qp)
			racereleaseg(sg.g, qp)
			c.recvx++
			if c.recvx == c.dataqsiz {
				c.recvx = 0
			}
			c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz
		}
	}
	if sg.elem != nil { // 元素不为空,直接发送
		sendDirect(c.elemtype, sg, ep)
		sg.elem = nil
	}
	gp := sg.g
	unlockf()
	gp.param = unsafe.Pointer(sg)
	if sg.releasetime != 0 {
		sg.releasetime = cputicks()
	}
	goready(gp, skip+1) // Question: 这里是用来做什么?
}

// Sends and receives on unbuffered or empty-buffered channels are the
// only operations where one running goroutine writes to the stack of
// another running goroutine. The GC assumes that stack writes only
// happen when the goroutine is running and are only done by that
// goroutine. Using a write barrier is sufficient to make up for
// violating that assumption, but the write barrier has to work.
// typedmemmove will call bulkBarrierPreWrite, but the target bytes
// are not in the heap, so that will not help. We arrange to call
// memmove and typeBitsBulkBarrier instead.
/**
 * 在一个无缓冲通道或空缓冲通道上发送和接收是一个正在运行的goroutine写入另一个正在运行的goroutine堆栈的唯一操作。
 * GC假定仅在goroutine运行时才发生堆栈写入,并且仅由该goroutine完成。 使用写屏障足以弥补违反该假设的缺点,
 * 但是写屏障必须起作用。 typedmemmove将调用bulkBarrierPreWrite,但是目标字节不在堆中,因此这无济于事。
 * 我们安排调用memmove和typeBitsBulkBarrier。
 * @param t 元素类型
 * @param sg
 * @param src 数据指针
 * @return
 **/
func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {
	// src is on our stack, dst is a slot on another stack.
	// src在我们的栈上,dst是另一个栈上的位置。

	// Once we read sg.elem out of sg, it will no longer
	// be updated if the destination's stack gets copied (shrunk).
	// So make sure that no preemption points can happen between read & use.
	// 一旦我们从sg中读取出sg.elem,如果目标堆栈被复制(缩小),它将不再被更新。
	// 因此,请确保在读取和使用之间没有任何抢占点。
	dst := sg.elem
	// 带屏障的写操作,参见:runtime/mbitmap.go/typeBitsBulkBarrier方法
	typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size)
	// No need for cgo write barrier checks because dst is always
	// Go memory.
	// 不需要cgo写屏障检查,因为dst始终是Go内存。
	memmove(dst, src, t.size)
}

/**
 * 直接接收通道数据
 * @param
 * @return
 **/
func recvDirect(t *_type, sg *sudog, dst unsafe.Pointer) {
	// dst is on our stack or the heap, src is on another stack.
	// The channel is locked, so src will not move during this
	// operation.
	// dst在我们的栈或堆上,src在另一个栈上。 通道已锁定,因此src在此操作期间将不会移动。
	src := sg.elem
	typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size)
	memmove(dst, src, t.size)
}
/**
 * 关闭通道
 * @param
 * @return
 **/
func closechan(c *hchan) {
	if c == nil { // 通道为nil
		panic(plainError("close of nil channel"))
	}

	lock(&c.lock) // 锁定
	if c.closed != 0 { // 通道已经关闭,再关闭就报panic
		unlock(&c.lock) // 解锁
		panic(plainError("close of closed channel"))
	}

	if raceenabled { // 此值已经为false
		callerpc := getcallerpc()
		racewritepc(c.raceaddr(), callerpc, funcPC(closechan))
		racerelease(c.raceaddr())
	}

	c.closed = 1 // 标记通道已经关闭

	var glist gList

	// release all readers // 释放所有的接收者
	for {
		sg := c.recvq.dequeue() // 接收者队列
		if sg == nil { // 队列为空
			break
		}
		if sg.elem != nil {
			typedmemclr(c.elemtype, sg.elem) // 进行内存清理
			sg.elem = nil
		}
		if sg.releasetime != 0 {
			sg.releasetime = cputicks()
		}
		gp := sg.g
		gp.param = nil // 参数清零
		if raceenabled {
			raceacquireg(gp, c.raceaddr())
		}
		glist.push(gp) // gp入队头
	}

	// release all writers (they will panic)
	// 释放所有写对象(他们会恐慌)
	for {
		sg := c.sendq.dequeue() // 发送者队列
		if sg == nil { // 队列为空
			break
		}
		sg.elem = nil // 直接清空内存?这么做不会内存溢出?
		if sg.releasetime != 0 {
			sg.releasetime = cputicks()
		}
		gp := sg.g
		gp.param = nil // 参数清零
		if raceenabled {
			raceacquireg(gp, c.raceaddr())
		}
		glist.push(gp) // gp入队头
	}
	unlock(&c.lock)

	// Ready all Gs now that we've dropped the channel lock.
	// 现在我们已经释放了通道锁,准备好所有G。
	for !glist.empty() { // 对所有的g,将schedlink清0,并且设置已经已经准备好
		gp := glist.pop()
		gp.schedlink = 0
		goready(gp, 3)
	}
}

// entry points for <- c from compiled code
//go:nosplit
/**
 * 编译代码中 <-c 的入口点
 * @param
 * @return
 **/
func chanrecv1(c *hchan, elem unsafe.Pointer) {
	chanrecv(c, elem, true)
}

//go:nosplit
/**
 *
 * @param
 * @return received true:表示已经接收到
 **/
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {
	_, received = chanrecv(c, elem, true)
	return
}

// chanrecv receives on channel c and writes the received data to ep.
// ep may be nil, in which case received data is ignored.
// If block == false and no elements are available, returns (false, false).
// Otherwise, if c is closed, zeros *ep and returns (true, false).
// Otherwise, fills in *ep with an element and returns (true, true).
// A non-nil ep must point to the heap or the caller's stack.
/**
 * chanrecv在通道c上接收并将接收到的数据写入ep。
 * ep可能为nil,在这种情况下,接收到的数据将被忽略。
 * 如果block == false并且没有可用元素,则返回(false,false)。
 * 否则,如果c关闭,则* ep为零并返回(true,false)。
 * 否则,用一个元素填充* ep并返回(true,true)。
 * 非nil必须指向堆或调用者的堆栈。
 * @param c 通道对象
 * @param ep 用于接收数据的指针
 * @param block true: 表示阻塞
 * @return selected true表示被选择
 * @return received true表示已经接收到值
 **/
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
	// raceenabled: don't need to check ep, as it is always on the stack
	// or is new memory allocated by reflect.
	// raceenabled:不需要检查ep,因为它始终在堆栈中,或者是反射所分配的新内存。

	if debugChan { // 此值已经为false
		print("chanrecv: chan=", c, "\n")
	}

	if c == nil { // 通道为空
		if !block { // 非阻塞
			return
		}
		// 阻塞状态
        // 将当前goroutine置于等待状态并调用unlockf。
		gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
		throw("unreachable")
	}

	// Fast path: check for failed non-blocking operation without acquiring the lock.
	//
	// After observing that the channel is not ready for receiving, we observe that the
	// channel is not closed. Each of these observations is a single word-sized read
	// (first c.sendq.first or c.qcount, and second c.closed).
	// Because a channel cannot be reopened, the later observation of the channel
	// being not closed implies that it was also not closed at the moment of the
	// first observation. We behave as if we observed the channel at that moment
	// and report that the receive cannot proceed.
	//
	// The order of operations is important here: reversing the operations can lead to
	// incorrect behavior when racing with a close.
	// 快速路径:不获取锁定而检查失败的非阻塞操作。
    //
    // 在观察到通道尚未准备好接收之后,我们观察到通道未关闭。 这些观察中的每一个都是单个字(word)大小的读取
    // (第一个c.sendq.first或c.qcount,第二个c.closed)。
    // 由于无法重新打开通道,因此对通道未关闭的后续观察意味着它在第一次观察时也未关闭。
    // 我们的行为就好像我们当时在观察该通道,并报告接收无法继续进行。
    //
    // 操作顺序在这里很重要:在进行抢占关闭时,反转操作可能导致错误的行为。
    // !block : 非阻塞
    // c.dataqsiz == 0 && c.sendq.first == nil : 通道没有空间,并且没有发送者
    // c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0 :  通道中有空间,并且没有数据
    // atomic.Load(&c.closed) : 通道未关闭
	if !block && (c.dataqsiz == 0 && c.sendq.first == nil ||
		c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0) &&
		atomic.Load(&c.closed) == 0 {
		return
	}

	var t0 int64
	if blockprofilerate > 0 {
		t0 = cputicks()
	}

	lock(&c.lock) // 加锁

	if c.closed != 0 && c.qcount == 0 { // 通道已经关闭,并且通道中没有数据
		if raceenabled {
			raceacquire(c.raceaddr())
		}
		unlock(&c.lock) // 解锁
		if ep != nil {
			typedmemclr(c.elemtype, ep) // 进行内存清理
		}
		return true, false
	}

	if sg := c.sendq.dequeue(); sg != nil { // 发送者队例不为空
		// Found a waiting sender. If buffer is size 0, receive value
		// directly from sender. Otherwise, receive from head of queue
		// and add sender's value to the tail of the queue (both map to
		// the same buffer slot because the queue is full).
		// 找到了等待发送者。 如果缓冲区的大小为0,则直接从发送方接收值。
		// 否则,从队列的开头接收并将发件人的值添加到队列的末尾(由于队列已满,因此两者都映射到同一缓冲区)。
		recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
		return true, true
	}

    // 没有发送者,数据队列不为空
	if c.qcount > 0 {
		// Receive directly from queue
		// 直接从数据队列中接收数据
		qp := chanbuf(c, c.recvx)
		if raceenabled {raceacquire(qp)
			racerelease(qp)
		}
		if ep != nil { // 进行内存数据移动
			typedmemmove(c.elemtype, ep, qp)
		}
		typedmemclr(c.elemtype, qp) // 清理qp的c.elemtype类型内存数据
		c.recvx++ // 指向下一个接收位置
		if c.recvx == c.dataqsiz { // 说明已经指向了队列末尾了的下一个位置了
			c.recvx = 0 // 重新指向头部
		}
		c.qcount-- // 数据队列中的数据减少一个
		unlock(&c.lock) // 解锁
		return true, true
	}

    // 没有发送者,并且队列为空,并且是非阻塞状态
	if !block {
		unlock(&c.lock)
		return false, false
	}

	// no sender available: block on this channel.
	// 没有可用的发送者:在此通道阻塞。
	gp := getg()
	mysg := acquireSudog()
	mysg.releasetime = 0
	if t0 != 0 {
		mysg.releasetime = -1
	}
	// No stack splits between assigning elem and enqueuing mysg
	// on gp.waiting where copystack can find it.
	// 在分配elem和将mysg入队到gp.waitcopy可以找到它的地方之间没有堆栈拆分。
	mysg.elem = ep
	mysg.waitlink = nil
	gp.waiting = mysg
	mysg.g = gp
	mysg.isSelect = false
	mysg.c = c
	gp.param = nil
	c.recvq.enqueue(mysg)
	// 阻塞状态
    // 将当前goroutine置于等待状态并调用unlockf。
	gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceEvGoBlockRecv, 2)

	// someone woke us up
	// 有人把我们唤醒了
	if mysg != gp.waiting {
		throw("G waiting list is corrupted")
	}
	gp.waiting = nil
	gp.activeStackChans = false
	if mysg.releasetime > 0 {
		blockevent(mysg.releasetime-t0, 2)
	}
	closed := gp.param == nil
	gp.param = nil
	mysg.c = nil
	releaseSudog(mysg) // 释放sudog
	return true, !closed
}

// recv processes a receive operation on a full channel c.
// There are 2 parts:
// 1) The value sent by the sender sg is put into the channel
//    and the sender is woken up to go on its merry way.
// 2) The value received by the receiver (the current G) is
//    written to ep.
// For synchronous channels, both values are the same.
// For asynchronous channels, the receiver gets its data from
// the channel buffer and the sender's data is put in the
// channel buffer.
// Channel c must be full and locked. recv unlocks c with unlockf.
// sg must already be dequeued from c.
// A non-nil ep must point to the heap or the caller's stack.
/**
 * recv在完整通道c上处理接收操作。
 * 有2个部分:
 *      1)将发送方sg发送的值放入通道中,并唤醒发送方以继续进行。
 *      2)接收方接收到的值(当前G)被写入ep。
 * 对于同步通道,两个值相同。
 * 对于异步通道,接收者从通道缓冲区获取数据,而发送者的数据放入通道缓冲区。
 * 频道c必须已满且已锁定。 recv用unlockf解锁c。
 * sg必须已经从c中出队。
 * 非nil必须指向堆或调用者的堆栈。
 * @param c 通道对象针对
 * @param sg
 * @param ep 用户接收元素的指针
 * @param unlockf 解锁函数
 * @param skip
 * @return
 **/
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
	if c.dataqsiz == 0 { // 数据队列的大小是0,表示这上一个无缓冲的通道
		if raceenabled {
			racesync(c, sg)
		}
		if ep != nil { // 元素指针不为空
			// copy data from sender
			// 直接从发送发拷贝数据
			recvDirect(c.elemtype, sg, ep)
		}
	} else { // 有缓冲通道
		// Queue is full. Take the item at the
		// head of the queue. Make the sender enqueue
		// its item at the tail of the queue. Since the
		// queue is full, those are both the same slot.
		// 队列已满。 将item放在队列的开头。 使发送者将其item排入队列的末尾。
		// 由于队列已满,因此它们都是相同的槽位。
		qp := chanbuf(c, c.recvx)
		if raceenabled { // 此值已经为false
			raceacquire(qp)
			racerelease(qp)
			raceacquireg(sg.g, qp)
			racereleaseg(sg.g, qp)
		}
		// copy data from queue to receiver
		if ep != nil {
			typedmemmove(c.elemtype, ep, qp)
		}
		// copy data from sender to queue
		// 将数据从队列复制到接收者
		typedmemmove(c.elemtype, qp, sg.elem)
		c.recvx++ // 指向下一个接收位置
		if c.recvx == c.dataqsiz { // 已经达到了末尾的下一个位置,需要重新指向头部
			c.recvx = 0
		}
		c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz // 发送者位置前移
	}
	sg.elem = nil
	gp := sg.g
	unlockf() // 解锁
	gp.param = unsafe.Pointer(sg)
	if sg.releasetime != 0 {
		sg.releasetime = cputicks()
	}
	// 将当前goroutine置于等待状态并解锁。 可以通过调用goready(gp)使goroutine重新运行
	goready(gp, skip+1) // 标记go
}

/**
 * 
 * @param 
 * @return 
 **/
func chanparkcommit(gp *g, chanLock unsafe.Pointer) bool {
	// There are unlocked sudogs that point into gp's stack. Stack
	// copying must lock the channels of those sudogs.
	// 有未锁定的sudog指向gp的堆栈。堆栈复制必须锁定那些sudog的通道。
	// activeStackChans指示存在指向该goroutine堆栈的未锁定通道。 
	// 如果为true,则堆栈复制需要获取通道锁以保护堆栈的这些区域。
	gp.activeStackChans = true
	unlock((*mutex)(chanLock)) // 解锁
	return true
}

// compiler implements
//
// select {
// case c <- v:
//		... foo
//	default:
//		... bar
//	}
//
// as
//
//	if selectnbsend(c, v) {
//		... foo
//	} else {
//		... bar
//	}
//
/**
 * 编译器实现,将goroutine的select send(case c <- v)语句转成对应的方法执行
 * @param
 * @return
 **/
func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {
	return chansend(c, elem, false, getcallerpc())
}

// compiler implements
//
//	select {
//	case v = <-c:
//		... foo
//	default:
//		... bar
//	}
//
// as
//
//	if selectnbrecv(&v, c) {
//		... foo
//	} else {
//		... bar
//	}
//
/**
 * 编译器实现,将goroutine的select receive(v = <-c)语句转成对应的方法执行
 * @param
 * @return
 **/
func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected bool) {
	selected, _ = chanrecv(c, elem, false)
	return
}

// compiler implements
//
//	select {
//	case v, ok = <-c:
//		... foo
//	default:
//		... bar
//	}
//
// as
//
//	if c != nil && selectnbrecv2(&v, &ok, c) {
//		... foo
//	} else {
//		... bar
//	}
//
/**
 * 编译器实现,将goroutine的select receive(case v, ok = <-c:)语句转成对应的方法执行
 * @param
 * @return
 **/
func selectnbrecv2(elem unsafe.Pointer, received *bool, c *hchan) (selected bool) {
	// TODO(khr): just return 2 values from this function, now that it is in Go.
	// TODO(khr):此函数位于Go中,只需返回2个值即可。表示这是一个历史遗留
	selected, *received = chanrecv(c, elem, false)
	return
}

//go:linkname reflect_chansend reflect.chansend
/**
 *
 * go:linkname引导编译器将当前(私有)方法或者变量在编译时链接到指定的位置的方法或者变量,
 * 第一个参数表示当前方法或变量,第二个参数表示目标方法或变量,因为这关指令会破坏系统和包的模块化,
 * 因此在使用时必须导入unsafe
 * 参见:https://blog.csdn.net/lastsweetop/article/details/78830772
 * @param
 * @return
 **/
func reflect_chansend(c *hchan, elem unsafe.Pointer, nb bool) (selected bool) {
	return chansend(c, elem, !nb, getcallerpc())
}

//go:linkname reflect_chanrecv reflect.chanrecv
func reflect_chanrecv(c *hchan, nb bool, elem unsafe.Pointer) (selected bool, received bool) {
	return chanrecv(c, elem, !nb)
}

//go:linkname reflect_chanlen reflect.chanlen
func reflect_chanlen(c *hchan) int {
	if c == nil {
		return 0
	}
	return int(c.qcount)
}

//go:linkname reflectlite_chanlen internal/reflectlite.chanlen
func reflectlite_chanlen(c *hchan) int {
	if c == nil {
		return 0
	}
	return int(c.qcount)
}

//go:linkname reflect_chancap reflect.chancap
func reflect_chancap(c *hchan) int {
	if c == nil {
		return 0
	}
	return int(c.dataqsiz)
}

//go:linkname reflect_chanclose reflect.chanclose
func reflect_chanclose(c *hchan) {
	closechan(c)
}

/**
 * 入队操作
 * @param sudog 需要入队的元素
 * @return
 **/
func (q *waitq) enqueue(sgp *sudog) {
	sgp.next = nil
	x := q.last
	if x == nil { // 队列中没有元素
		sgp.prev = nil
		q.first = sgp
		q.last = sgp
		return
	}

	// 队列中已经有元素
	sgp.prev = x
	x.next = sgp
	q.last = sgp
}

/**
 * 出队
 * @param
 * @return
 **/
func (q *waitq) dequeue() *sudog {
	for {
		sgp := q.first
		if sgp == nil { // 队列中没有元素
			return nil
		}
		y := sgp.next
		if y == nil { // 队列中只有一个元素
			q.first = nil
			q.last = nil
		} else {
			y.prev = nil
			q.first = y
			sgp.next = nil // mark as removed (see dequeueSudog) // 标记为已删除(请参阅dequeueSudog)
		}

		// if a goroutine was put on this queue because of a
		// select, there is a small window between the goroutine
		// being woken up by a different case and it grabbing the
		// channel locks. Once it has the lock
		// it removes itself from the queue, so we won't see it after that.
		// We use a flag in the G struct to tell us when someone
		// else has won the race to signal this goroutine but the goroutine
		// hasn't removed itself from the queue yet.
		// 如果由于选择而将goroutine放在此队列中,则在其他情况下唤醒goroutine并获取通道锁之间会有一个小窗口。
		// 一旦拥有了锁,它就会将自己从队列中删除,因此之后我们将看不到它。
        // 我们在G结构中使用一个标志来告诉我们何时其他人赢得了发信号通知此goroutine的竞赛,
        // 但goroutine尚未将自己从队列中删除。
		if sgp.isSelect && !atomic.Cas(&sgp.g.selectDone, 0, 1) {
			continue
		}

		return sgp
	}
}

func (c *hchan) raceaddr() unsafe.Pointer {
	// Treat read-like and write-like operations on the channel to
	// happen at this address. Avoid using the address of qcount
	// or dataqsiz, because the len() and cap() builtins read
	// those addresses, and we don't want them racing with
	// operations like close().
	// 将通道上的读取和写入操作视为在此地址发生。 避免使用qcount或dataqsiz的地址,
	// 因为内置的len()和cap()会读取这些地址,并且我们不希望它们与close()之类的操作竞争。
	return unsafe.Pointer(&c.buf)
}

/**
 * 这个方法现在看来没有什么用了
 * @param
 * @return
 **/
func racesync(c *hchan, sg *sudog) {
	racerelease(chanbuf(c, 0))
	raceacquireg(sg.g, chanbuf(c, 0))
	racereleaseg(sg.g, chanbuf(c, 0))
	raceacquire(chanbuf(c, 0))
}
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页

打赏

Wang-Junchao

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值