18-chan原理2-读取

4. 读取chan

由汇编可以看出,读取一个chan,转化为对runtime.chanrecv1()的调用。如果采用两个返回值方式,则转化为runtime.chanrecv2()调用,区别在于返回一个bool值,标识是否接收到了数据。

  1. 参数1,chan本身。
  2. 参数2,接收读取数据的变量。
var var_chan_1 = make(chan int)
var_read = <-var_chan_1


        0x00c7 00199 (channel.go:30)    MOVQ    $0, ""..autotmp_6+64(SP)
        0x00d0 00208 (channel.go:30)    MOVQ    "".var_chan_1+96(SP), AX
        0x00d5 00213 (channel.go:30)    MOVQ    AX, (SP)
        0x00d9 00217 (channel.go:30)    LEAQ    ""..autotmp_6+64(SP), AX
        0x00de 00222 (channel.go:30)    MOVQ    AX, 8(SP)
        0x00e3 00227 (channel.go:30)    CALL    runtime.chanrecv1(SB)
        0x00e8 00232 (channel.go:30)    MOVQ    ""..autotmp_6+64(SP), AX
        0x00ed 00237 (channel.go:30)    MOVQ    AX, "".var_read+48(SP)

4.1 runtime.chanrecv1()

runtime.chanrecv1()是对 chanrecv()的封装。参数true表示阻塞调用。

// entry points for <- c from compiled code
//go:nosplit
func chanrecv1(c *hchan, elem unsafe.Pointer) {
	chanrecv(c, elem, true)
}

//go:nosplit
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {
	_, received = chanrecv(c, elem, true)
	return
}
  1. 如果chan是nil
    1) 阻塞调用,则panic。
    2) 非阻塞调用则直接返回。
  2. 对于非阻塞调用,进行快速判断是否有数据可读(避免对chan加锁)。
    1. chan未被关闭.
    2. 循环队列(dataqsiz长度为0,发送队列sendq为空。或者 循环队列dataqsiz不为空,但是队列中数据长度qcount为0.
  3. 对chan加锁。
  4. chan已被关闭,且没有数据可读取,直接返回.
    如果此时chan仍未关闭,并且数据队列长度为0,则将接收返回值的对象清空。返回接收结束(true-接收成功,false-没有接收到数据)。
  5. 队列已满
    如果等待发送队列sendq不空,则直接将发送方的数据copy到接收对象,返回。
  6. 队列未满
    如果队列qcount中数据不为空,则从队列中取出数据。返回。
  7. 无数据可读
    1. 非阻塞调用,直接放回。
      如果队列qcount中没数据,待发送队列sendq也为空,并且非阻塞调用,直接返回。
    2. 阻塞调用,阻塞等待
      创建一个sudog对象,放在接收等待队列recvq中。调用gopark()则设置抢占标记,并调用park_m()将G与M解除绑定,然后触发调度器调用schedule(),将当前goroutine挂起。

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

	if c == nil {
		if !block {
			return
		}
		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.
	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).
		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)
		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.
	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)
	goparkunlock(&c.lock, waitReasonChanReceive, traceEvGoBlockRecv, 3)

	// someone woke us up
	if mysg != gp.waiting {
		throw("G waiting list is corrupted")
	}
	gp.waiting = nil
	if mysg.releasetime > 0 {
		blockevent(mysg.releasetime-t0, 2)
	}
	closed := gp.param == nil
	gp.param = nil
	mysg.c = nil
	releaseSudog(mysg)
	return true, !closed
}

4.2 recv()

  1. 如果是无缓冲chan,则直接将发送方的数据直接copy到接收对象上。
  2. 如果是带缓冲chan,将数据从接收缓存开始位置recvxcopy到接收对象。将数据从发送方copy到队列。接收位置更新recvx++,如果超出缓存大小,重置到开始位置。重置发送位置为接收位置。
  3. 调用goready(),设置goroutine状态为_Grunnable
// 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.
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
	if c.dataqsiz == 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.
		qp := chanbuf(c, c.recvx)
		if raceenabled {
			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()
	}
	goready(gp, skip+1)
}

你可能感兴趣的:(Go,实现原理,chan,源码分析)