前言

select作为go chan通信的重要监听工具,有着很广泛的使用场景。select的使用主要是搭配通信case使用,表面上看,只是简单的selectcase搭配,实际上根据case的数量及类型,在编译时select会进行优化处理,根据不同的情况调用不同的底层逻辑。

select的编译处理

select编译时的核心处理逻辑如下:

func walkselectcases(cases *nodes) []*node {
	ncas := cases.len()
	sellineno := lineno

	// optimization: zero-case select
	// 针对没有case的select优化
	if ncas == 0 {
		return []*node{mkcall("block", nil, nil)}
	}

	// optimization: one-case select: single op.
	// 针对1个case(单个操作)select的优化
	if ncas == 1 {
		cas := cases.first()
		setlineno(cas)
		l := cas.ninit.slice()
		if cas.left != nil { // not default: 非default case
			n := cas.left // 获取case表达式
			l = append(l, n.ninit.slice()...)
			n.ninit.set(nil)
			switch n.op {
			default:
				fatalf("select %v", n.op)

			case osend: // left <- right
				// already ok
				// n中已包含left/right
			
			case oselrecv, oselrecv2: // oselrecv(left = <-right.left) oselrecv2(list = <-right.left)
				if n.op == oselrecv || n.list.len() == 0 { // 左侧有0或1个接收者
					if n.left == nil { // 没有接收者
						n = n.right // 只需保留右侧
					} else { // 
						n.op = oas // 只有一个接收者,更新op为oas
					}
					break
				}

				if n.left == nil { // 检查是否表达式或赋值
					nblank = typecheck(nblank, ctxexpr|ctxassign)
					n.left = nblank
				}

				n.op = oas2 // oselrecv2多个接收者
				n.list.prepend(n.left) // 将left放在前面
				n.rlist.set1(n.right) 
				n.right = nil
				n.left = nil
				n.settypecheck(0)
				n = typecheck(n, ctxstmt)
			}

			l = append(l, n)
		}

		l = append(l, cas.nbody.slice()...) // case内的处理
		l = append(l, nod(obreak, nil, nil)) // 添加break
		return l
	}

	// convert case value arguments to addresses.
	// this rewrite is used by both the general code and the next optimization.
	var dflt *node
	for _, cas := range cases.slice() {
		setlineno(cas)
		n := cas.left
		if n == nil {
			dflt = cas
			continue
		}
		switch n.op {
		case osend:
			n.right = nod(oaddr, n.right, nil)
			n.right = typecheck(n.right, ctxexpr)

		case oselrecv, oselrecv2:
			if n.op == oselrecv2 && n.list.len() == 0 {
				n.op = oselrecv
			}

			if n.left != nil {
				n.left = nod(oaddr, n.left, nil)
				n.left = typecheck(n.left, ctxexpr)
			}
		}
	}

	// optimization: two-case select but one is default: single non-blocking op.
	if ncas == 2 && dflt != nil {
		cas := cases.first()
		if cas == dflt {
			cas = cases.second()
		}

		n := cas.left
		setlineno(n)
		r := nod(oif, nil, nil)
		r.ninit.set(cas.ninit.slice())
		switch n.op {
		default:
			fatalf("select %v", n.op)

		case osend:
			// if selectnbsend(c, v) { body } else { default body }
			ch := n.left
			r.left = mkcall1(chanfn("selectnbsend", 2, ch.type), types.types[tbool], &r.ninit, ch, n.right)

		case oselrecv:
			// if selectnbrecv(&v, c) { body } else { default body }
			ch := n.right.left
			elem := n.left
			if elem == nil {
				elem = nodnil()
			}
			r.left = mkcall1(chanfn("selectnbrecv", 2, ch.type), types.types[tbool], &r.ninit, elem, ch)

		case oselrecv2:
			// if selectnbrecv2(&v, &received, c) { body } else { default body }
			ch := n.right.left
			elem := n.left
			if elem == nil {
				elem = nodnil()
			}
			receivedp := nod(oaddr, n.list.first(), nil)
			receivedp = typecheck(receivedp, ctxexpr)
			r.left = mkcall1(chanfn("selectnbrecv2", 2, ch.type), types.types[tbool], &r.ninit, elem, receivedp, ch)
		}

		r.left = typecheck(r.left, ctxexpr)
		r.nbody.set(cas.nbody.slice())
		r.rlist.set(append(dflt.ninit.slice(), dflt.nbody.slice()...))
		return []*node{r, nod(obreak, nil, nil)}
	}

	if dflt != nil {
		ncas--
	}
	casorder := make([]*node, ncas)
	nsends, nrecvs := 0, 0

	var init []*node

	// generate sel-struct
	lineno = sellineno
	selv := temp(types.newarray(scasetype(), int64(ncas)))
	r := nod(oas, selv, nil)
	r = typecheck(r, ctxstmt)
	init = append(init, r)

	// no initialization for order; runtime.selectgo is responsible for that.
	order := temp(types.newarray(types.types[tuint16], 2*int64(ncas)))

	var pc0, pcs *node
	if flag_race {
		pcs = temp(types.newarray(types.types[tuintptr], int64(ncas)))
		pc0 = typecheck(nod(oaddr, nod(oindex, pcs, nodintconst(0)), nil), ctxexpr)
	} else {
		pc0 = nodnil()
	}

	// register cases
	for _, cas := range cases.slice() {
		setlineno(cas)

		init = append(init, cas.ninit.slice()...)
		cas.ninit.set(nil)

		n := cas.left
		if n == nil { // default:
			continue
		}

		var i int
		var c, elem *node
		switch n.op {
		default:
			fatalf("select %v", n.op)
		case osend:
			i = nsends
			nsends++
			c = n.left
			elem = n.right
		case oselrecv, oselrecv2:
			nrecvs++
			i = ncas - nrecvs
			c = n.right.left
			elem = n.left
		}

		casorder[i] = cas

		setfield := func(f string, val *node) {
			r := nod(oas, nodsym(odot, nod(oindex, selv, nodintconst(int64(i))), lookup(f)), val)
			r = typecheck(r, ctxstmt)
			init = append(init, r)
		}

		c = convnop(c, types.types[tunsafeptr])
		setfield("c", c)
		if elem != nil {
			elem = convnop(elem, types.types[tunsafeptr])
			setfield("elem", elem)
		}

		// todo(mdempsky): there should be a cleaner way to
		// handle this.
		if flag_race {
			r = mkcall("selectsetpc", nil, nil, nod(oaddr, nod(oindex, pcs, nodintconst(int64(i))), nil))
			init = append(init, r)
		}
	}
	if nsends+nrecvs != ncas {
		fatalf("walkselectcases: miscount: %v + %v != %v", nsends, nrecvs, ncas)
	}

	// run the select
	lineno = sellineno
	chosen := temp(types.types[tint])
	recvok := temp(types.types[tbool])
	r = nod(oas2, nil, nil)
	r.list.set2(chosen, recvok)
	fn := syslook("selectgo")
	r.rlist.set1(mkcall1(fn, fn.type.results(), nil, byteptrtoindex(selv, 0), byteptrtoindex(order, 0), pc0, nodintconst(int64(nsends)), nodintconst(int64(nrecvs)), nodbool(dflt == nil)))
	r = typecheck(r, ctxstmt)
	init = append(init, r)

	// selv and order are no longer alive after selectgo.
	init = append(init, nod(ovarkill, selv, nil))
	init = append(init, nod(ovarkill, order, nil))
	if flag_race {
		init = append(init, nod(ovarkill, pcs, nil))
	}

	// dispatch cases
	dispatch := func(cond, cas *node) {
		cond = typecheck(cond, ctxexpr)
		cond = defaultlit(cond, nil)

		r := nod(oif, cond, nil)

		if n := cas.left; n != nil && n.op == oselrecv2 {
			x := nod(oas, n.list.first(), recvok)
			x = typecheck(x, ctxstmt)
			r.nbody.append(x)
		}

		r.nbody.appendnodes(&cas.nbody)
		r.nbody.append(nod(obreak, nil, nil))
		init = append(init, r)
	}

	if dflt != nil {
		setlineno(dflt)
		dispatch(nod(olt, chosen, nodintconst(0)), dflt)
	}
	for i, cas := range casorder {
		setlineno(cas)
		dispatch(nod(oeq, chosen, nodintconst(int64(i))), cas)
	}

	return init
}

select编译时会根据case的数量进行优化:

1.没有case
直接调用block

2.1个case
(1)default case,直接执行body
(2) send/recv case (block为true),按照单独执行的结果确认,可能会发生block
(3) send调用对应的chansend1
(4) recv调用对应的chanrecv1/chanrecv2

3.2个case且包含一个default case
(1) send/recv case (block为false),按照单独执行的结果确认case是否ok,!ok则执行default case,不会发生block
(2) send调用对应的selectnbsend
(3) recv调用对应的selectnbrecv/selectnbrecv2

4.一般的case
selectgo

总结

最后,以一张图进行简单总结。

以上就是go select编译期的优化处理逻辑使用场景分析的详细内容,更多关于go select编译的资料请关注www.887551.com其它相关文章!