go-select源码分析


| 阅读 |,阅读约 4 分钟
| 复制链接:

Overview

select简介

select使用

下面是select的最简单的用法:

1

select源码入口

select在源码中也没有对应的实现,而是通过编译器将相关符号翻译为底层实现。 使用以下命令将go源码翻译为汇编

1go tool compile -N -l -S main.go>hello.s

查看部分带有CALL指令的核心内容如下:

10x0102 00258 (main.go:64) CALL runtime.selectgo(SB)

可以猜测对应关系:

  • select语句对应:runtime.selectgo函数

相关源码只需要到runtime包下,全局搜索就可以找到在文件runtime/chan.go下

1func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {}

源码分析

case数据结构

1type scase struct {
2  c           *hchan         // chan
3  elem        unsafe.Pointer // data element
4  kind        uint16
5  pc          uintptr // race pc (for race detector / msan)
6  releasetime int64
7}

编译器

源码位置:src/cmd/compile/internal/gc/select.go

  1func walkselect(sel *Node) {
  2  lno := setlineno(sel)
  3  if sel.Nbody.Len() != 0 {
  4    Fatalf("double walkselect")
  5  }
  6
  7  init := sel.Ninit.Slice()
  8  sel.Ninit.Set(nil)
  9
 10  init = append(init, walkselectcases(&sel.List)...)
 11  sel.List.Set(nil)
 12
 13  sel.Nbody.Set(init)
 14  walkstmtlist(sel.Nbody.Slice())
 15
 16  lineno = lno
 17}
 18
 19func walkselectcases(cases *Nodes) []*Node {
 20  n := cases.Len()
 21  sellineno := lineno
 22
 23  // optimization: zero-case select
 24  // 如果没有case,直接调用block函数,内部是调用的gopark阻塞
 25  if n == 0 {
 26    return []*Node{mkcall("block", nil, nil)}
 27  }
 28
 29  // optimization: one-case select: single op.
 30  // TODO(rsc): Reenable optimization once order.go can handle it.
 31  // golang.org/issue/7672.
 32  // 只有一个case的情况
 33  if n == 1 {
 34    cas := cases.First()
 35    setlineno(cas)
 36    l := cas.Ninit.Slice()
 37    // 没有default的处理逻辑
 38    if cas.Left != nil { // not default:
 39      n := cas.Left
 40      l = append(l, n.Ninit.Slice()...)
 41      n.Ninit.Set(nil)
 42      var ch *Node
 43      switch n.Op {
 44      default:
 45        Fatalf("select %v", n.Op)
 46
 47        // ok already
 48      case OSEND:
 49        ch = n.Left
 50
 51      case OSELRECV, OSELRECV2:
 52        ch = n.Right.Left
 53        if n.Op == OSELRECV || n.List.Len() == 0 {
 54          if n.Left == nil {
 55            n = n.Right
 56          } else {
 57            n.Op = OAS
 58          }
 59          break
 60        }
 61
 62        if n.Left == nil {
 63          nblank = typecheck(nblank, ctxExpr|ctxAssign)
 64          n.Left = nblank
 65        }
 66
 67        n.Op = OAS2
 68        n.List.Prepend(n.Left)
 69        n.Rlist.Set1(n.Right)
 70        n.Right = nil
 71        n.Left = nil
 72        n.SetTypecheck(0)
 73        n = typecheck(n, ctxStmt)
 74      }
 75
 76      // if ch == nil { block() }; n;
 77      a := nod(OIF, nil, nil)
 78
 79      a.Left = nod(OEQ, ch, nodnil())
 80      var ln Nodes
 81      ln.Set(l)
 82      a.Nbody.Set1(mkcall("block", nil, &ln))
 83      l = ln.Slice()
 84      a = typecheck(a, ctxStmt)
 85      l = append(l, a, n)
 86    }
 87
 88    l = append(l, cas.Nbody.Slice()...)
 89    l = append(l, nod(OBREAK, nil, nil))
 90    return l
 91  }
 92
 93  // convert case value arguments to addresses.
 94  // this rewrite is used by both the general code and the next optimization.
 95  // 多个case的情况,把每个分支转换为case结构体
 96  for _, cas := range cases.Slice() {
 97    setlineno(cas)
 98    n := cas.Left
 99    if n == nil {
100      continue
101    }
102    switch n.Op {
103    case OSEND:
104      n.Right = nod(OADDR, n.Right, nil)
105      n.Right = typecheck(n.Right, ctxExpr)
106
107    case OSELRECV, OSELRECV2:
108      if n.Op == OSELRECV2 && n.List.Len() == 0 {
109        n.Op = OSELRECV
110      }
111
112      if n.Left != nil {
113        n.Left = nod(OADDR, n.Left, nil)
114        n.Left = typecheck(n.Left, ctxExpr)
115      }
116    }
117  }
118
119  // optimization: two-case select but one is default: single non-blocking op.
120  if n == 2 && (cases.First().Left == nil || cases.Second().Left == nil) {
121    var cas *Node
122    var dflt *Node
123    if cases.First().Left == nil {
124      cas = cases.Second()
125      dflt = cases.First()
126    } else {
127      dflt = cases.Second()
128      cas = cases.First()
129    }
130
131    n := cas.Left
132    setlineno(n)
133    r := nod(OIF, nil, nil)
134    r.Ninit.Set(cas.Ninit.Slice())
135    switch n.Op {
136    default:
137      Fatalf("select %v", n.Op)
138
139    case OSEND:
140      // if selectnbsend(c, v) { body } else { default body }
141      ch := n.Left
142      r.Left = mkcall1(chanfn("selectnbsend", 2, ch.Type), types.Types[TBOOL], &r.Ninit, ch, n.Right)
143
144    case OSELRECV:
145      // if selectnbrecv(&v, c) { body } else { default body }
146      r = nod(OIF, nil, nil)
147      r.Ninit.Set(cas.Ninit.Slice())
148      ch := n.Right.Left
149      elem := n.Left
150      if elem == nil {
151        elem = nodnil()
152      }
153      r.Left = mkcall1(chanfn("selectnbrecv", 2, ch.Type), types.Types[TBOOL], &r.Ninit, elem, ch)
154
155    case OSELRECV2:
156      // if selectnbrecv2(&v, &received, c) { body } else { default body }
157      r = nod(OIF, nil, nil)
158      r.Ninit.Set(cas.Ninit.Slice())
159      ch := n.Right.Left
160      elem := n.Left
161      if elem == nil {
162        elem = nodnil()
163      }
164      receivedp := nod(OADDR, n.List.First(), nil)
165      receivedp = typecheck(receivedp, ctxExpr)
166      r.Left = mkcall1(chanfn("selectnbrecv2", 2, ch.Type), types.Types[TBOOL], &r.Ninit, elem, receivedp, ch)
167    }
168
169    r.Left = typecheck(r.Left, ctxExpr)
170    r.Nbody.Set(cas.Nbody.Slice())
171    r.Rlist.Set(append(dflt.Ninit.Slice(), dflt.Nbody.Slice()...))
172    return []*Node{r, nod(OBREAK, nil, nil)}
173  }
174
175  var init []*Node
176
177  // generate sel-struct
178  lineno = sellineno
179  selv := temp(types.NewArray(scasetype(), int64(n)))
180  r := nod(OAS, selv, nil)
181  r = typecheck(r, ctxStmt)
182  init = append(init, r)
183
184  order := temp(types.NewArray(types.Types[TUINT16], 2*int64(n)))
185  r = nod(OAS, order, nil)
186  r = typecheck(r, ctxStmt)
187  init = append(init, r)
188
189  // register cases
190  for i, cas := range cases.Slice() {
191    setlineno(cas)
192
193    init = append(init, cas.Ninit.Slice()...)
194    cas.Ninit.Set(nil)
195
196    // Keep in sync with runtime/select.go.
197    const (
198      caseNil = iota
199      caseRecv
200      caseSend
201      caseDefault
202    )
203
204    var c, elem *Node
205    var kind int64 = caseDefault
206
207    if n := cas.Left; n != nil {
208      init = append(init, n.Ninit.Slice()...)
209
210      switch n.Op {
211      default:
212        Fatalf("select %v", n.Op)
213      case OSEND:
214        kind = caseSend
215        c = n.Left
216        elem = n.Right
217      case OSELRECV, OSELRECV2:
218        kind = caseRecv
219        c = n.Right.Left
220        elem = n.Left
221      }
222    }
223
224    setField := func(f string, val *Node) {
225      r := nod(OAS, nodSym(ODOT, nod(OINDEX, selv, nodintconst(int64(i))), lookup(f)), val)
226      r = typecheck(r, ctxStmt)
227      init = append(init, r)
228    }
229
230    setField("kind", nodintconst(kind))
231    if c != nil {
232      c = convnop(c, types.Types[TUNSAFEPTR])
233      setField("c", c)
234    }
235    if elem != nil {
236      elem = convnop(elem, types.Types[TUNSAFEPTR])
237      setField("elem", elem)
238    }
239
240    // TODO(mdempsky): There should be a cleaner way to
241    // handle this.
242    if instrumenting {
243      r = mkcall("selectsetpc", nil, nil, bytePtrToIndex(selv, int64(i)))
244      init = append(init, r)
245    }
246  }
247
248  // run the select
249  lineno = sellineno
250  chosen := temp(types.Types[TINT])
251  recvOK := temp(types.Types[TBOOL])
252  r = nod(OAS2, nil, nil)
253  r.List.Set2(chosen, recvOK)
254
255  // 调用selectgo,这个函数是决定了如何选择分支
256  fn := syslook("selectgo")
257  r.Rlist.Set1(mkcall1(fn, fn.Type.Results(), nil, bytePtrToIndex(selv, 0), bytePtrToIndex(order, 0), nodintconst(int64(n))))
258  r = typecheck(r, ctxStmt)
259  init = append(init, r)
260
261  // selv and order are no longer alive after selectgo.
262  init = append(init, nod(OVARKILL, selv, nil))
263  init = append(init, nod(OVARKILL, order, nil))
264
265  // dispatch cases
266  for i, cas := range cases.Slice() {
267    setlineno(cas)
268
269    cond := nod(OEQ, chosen, nodintconst(int64(i)))
270    cond = typecheck(cond, ctxExpr)
271    cond = defaultlit(cond, nil)
272
273    r = nod(OIF, cond, nil)
274
275    if n := cas.Left; n != nil && n.Op == OSELRECV2 {
276      x := nod(OAS, n.List.First(), recvOK)
277      x = typecheck(x, ctxStmt)
278      r.Nbody.Append(x)
279    }
280
281    r.Nbody.AppendNodes(&cas.Nbody)
282    r.Nbody.Append(nod(OBREAK, nil, nil))
283    init = append(init, r)
284  }
285
286  return init
287}

select.go

 1func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
 2  if debugSelect {
 3    print("select: cas0=", cas0, "\n")
 4  }
 5
 6  cas1 := (*[1 << 16]scase)(unsafe.Pointer(cas0))
 7  order1 := (*[1 << 17]uint16)(unsafe.Pointer(order0))
 8
 9  scases := cas1[:ncases:ncases]
10  pollorder := order1[:ncases:ncases]
11  lockorder := order1[ncases:][:ncases:ncases]
12
13  // 生成随机顺序
14  // generate permuted order
15  for i := 1; i < ncases; i++ {
16    j := fastrandn(uint32(i + 1))
17    pollorder[i] = pollorder[j]
18    pollorder[j] = uint16(i)
19  }
20  }