go|go select编译期的优化处理逻辑使用场景分析

前言 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 casen := 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/rightcase 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 = niln.Left = niln.SetTypecheck(0)n = typecheck(n, ctxStmt)}l = append(l, n)}l = append(l, cas.Nbody.Slice()...) // case内的处理l = append(l, nod(OBREAK, nil, nil)) // 添加breakreturn 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.Leftif n == nil {dflt = cascontinue}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.Leftsetlineno(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.Leftr.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.Leftelem := n.Leftif 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.Leftelem := n.Leftif 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.Leftif n == nil { // default:continue}var i intvar c, elem *Nodeswitch n.Op {default:Fatalf("select %v", n.Op)case OSEND:i = nsendsnsends++c = n.Leftelem = n.Rightcase OSELRECV, OSELRECV2:nrecvs++i = ncas - nrecvsc = n.Right.Leftelem = n.Left}casorder[i] = cassetField := 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|go select编译期的优化处理逻辑使用场景分析
文章图片

【go|go select编译期的优化处理逻辑使用场景分析】以上就是go select编译期的优化处理逻辑使用场景分析的详细内容,更多关于go select编译的资料请关注脚本之家其它相关文章!

    推荐阅读