006-Golang1.17源码分析之select

Golang1.17源码分析之select-006

Golang1.17 学习笔记006

源码地址:runtime/select.go

数据结构:

type scase struct {
	c    *hchan         // chan
	elem unsafe.Pointer // data element
}

核心函数:selectgo()

order0 为一个两倍 cas0 数组长度的 buffer,保存 scase 随机序列 pollorder 和 scase 中 channel 地址序列 lockorder

pollorder:每次selectgo执行都会把scase序列打乱,以达到随机检测case的目的

lockorder:所有case语句中channel序列,以达到去重防止对channel加锁时重复加锁的目的

func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, block bool) (int, bool) {
    // generate permuted order
    norder := 0
    for i := range scases {
        cas := &scases[i]
		// Omit cases without channels from the poll and lock orders.
		if cas.c == nil {
			cas.elem = nil // allow GC
			continue
		}
		// ....
		i++
    }
    pollorder = pollorder[:norder]
	lockorder = lockorder
	
	// sort the cases by Hchan address to get the locking order.
	// simple heap sort, to guarantee n log n time and constant stack footprint.
	// 一系列堆排序操作
	
	// lock all the channels involved in the select
	sellock(scases, lockorder)
	
	// pass 1 - look for something already waiting
	for _, casei := range pollorder {
	}
	
	if !block {
		selunlock(scases, lockorder)
		casi = -1
		goto retc
	}
	
	// pass 2 - enqueue on all chans
	gp = getg()
	if gp.waiting != nil {
		throw("gp.waiting != nil")
	}
	nextp = &gp.waiting
	for _, casei := range lockorder {
	}
	
	// wait for someone to wake us up
	gp.param = nil
	// Signal to anyone trying to shrink our stack that we're about
	// to park on a channel. The window between when this G's status
	// changes and when we set gp.activeStackChans is not safe for
	// stack shrinking.
	gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1)

	sellock(scases, lockorder)
	
	// pass 3 - dequeue from unsuccessful chans otherwise they stack up on quiet channels
	// record the successful case, if any. We singly-linked up the SudoGs in lock order.
	// Clear all elem before unlinking from gp.waiting.
	for sg1 := gp.waiting; sg1 != nil; sg1 = sg1.waitlink {
		sg1.isSelect = false
		sg1.elem = nil
		sg1.c = nil
	}
	gp.waiting = nil
	for _, casei := range lockorder {
	}
}

// 伪代码
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
    //1. 锁定scase语句中所有的channel
    //2. 按照随机顺序检测scase中的channel是否ready
    //   2.1 如果case可读,则读取channel中数据,解锁所有的channel,然后返回(case index, true)
    //   2.2 如果case可写,则将数据写入channel,解锁所有的channel,然后返回(case index, false)
    //   2.3 所有case都未ready,则解锁所有的channel,然后返回(default index, false)
    //3. 所有case都未ready,且没有default语句
    //   3.1 将当前协程加入到所有channel的等待队列
    //   3.2 当将协程转入阻塞,等待被唤醒
    //4. 唤醒后返回channel对应的case index
    //   4.1 如果是读操作,解锁所有的channel,然后返回(case index, true)
    //   4.2 如果是写操作,解锁所有的channel,然后返回(case index, false)
}

总结

  • select语句中除default外,各case执行顺序是随机的

  • select语句中如果没有default语句,则会阻塞等待任一case

参考文献:《Go专家编程》之 select

上一篇:二维数组的内存图


下一篇:RocketMQ-producer