• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    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 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编译的资料请关注脚本之家其它相关文章!

    您可能感兴趣的文章:
    • 详解Golang并发操作中常见的死锁情形
    • Go 语言中的死锁问题解决
    • Go语言死锁与goroutine泄露问题的解决
    • golang coroutine 的等待与死锁用法
    • Django实现jquery select2带搜索的下拉框
    • Go语言使用select{}阻塞main函数介绍
    • matplotlib之多边形选区(PolygonSelector)的使用
    • golang中的select关键字用法总结
    • Go select 死锁的一个细节
    上一篇:Go 语言下基于Redis分布式锁的实现方式
    下一篇:Golang的继承模拟实例
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

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