go-select源码分析
| 阅读 | 共 1659 字,阅读约
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 }