11# Select
22
3+ select 的实现在目前 Go 的 master 分支已经有所修改,所以本文只针对 1.10 之前的版本。
4+
35## 基本用法
46
57非阻塞 select:
@@ -24,6 +26,82 @@ select {
2426println (" can not be printed" )
2527```
2628
29+ for select 组合起来用很常见,不过需要注意,下面的两种场景可能会造成问题:
30+
31+ ``` go
32+ for {
33+ select {
34+ case d := <- ch:
35+ default :
36+ }
37+ }
38+ ```
39+
40+ 这种写法比较危险,如果 ch 中没有数据就会一直死循环 cpu 爆炸。
41+
42+ ``` go
43+ for {
44+ select {
45+ case d := <- ch:
46+ }
47+ }
48+ ```
49+
50+ 你以为这么写就没事了?啊当然,一般情况下还好。但如果 ch 被其它 goroutine close 掉了,那么 d:= <-ch 这种形式就是永远不阻塞,并且会一直返回零值了。如果不想关注这种情况,并且 select 中实际只操作一个 channel,建议写成 for range 形式:
51+
52+ ``` go
53+ for d := range ch {
54+ // do some happy things with d
55+ }
56+ ```
57+
58+ 这样 ch 关闭的时候 for 循环会自动退出。
59+
60+ 如果 select 中需要监听多个 channel,并且这些 channel 可能被关闭,那需要老老实实地写双返回值的 channel 取值表达式:
61+
62+ ``` go
63+ outer:
64+ for {
65+ select {
66+ case d , ok := <- ch1:
67+ if !ok {
68+ break outer
69+ }
70+ case d , ok := <- ch2:
71+ if !ok {
72+ break outer
73+ }
74+ }
75+ }
76+ ```
77+
78+ 当然,如果你不确定,可以用下面的 demo 进行验证:
79+
80+ ``` go
81+ package main
82+
83+ import " time"
84+
85+ func main () {
86+ var ch1 chan int
87+ var ch2 = make (chan int )
88+ close (ch2)
89+ go func () {
90+ for {
91+ select {
92+ case d := <- ch1:
93+ println (" ch1" , d)
94+ case d := <- ch2:
95+ println (" ch2" , d)
96+ }
97+ }
98+ }()
99+ time.Sleep (time.Hour )
100+ }
101+ ```
102+
103+ 尽管 ch2 已经被关闭,依然会不断地进入 case d:= <-ch2 中。因此在使用 for select 做设计时,请务必考虑当监听的 channel 在外部被正常或意外关闭后会有什么样的后果。
104+
27105## 源码分析
28106
29107### 数据结构
@@ -147,14 +225,37 @@ func selectsend(sel *hselect, c *hchan, elem unsafe.Pointer) {
147225}
148226```
149227
228+ ``` go
229+ // compiler implements
230+ //
231+ // select {
232+ // case c <- v:
233+ // ... foo
234+ // default:
235+ // ... bar
236+ // }
237+ //
238+ // as
239+ //
240+ // if selectnbsend(c, v) {
241+ // ... foo
242+ // } else {
243+ // ... bar
244+ // }
245+ //
246+ func selectnbsend (c *hchan , elem unsafe .Pointer ) (selected bool ) {
247+ return chansend (c, elem, false , getcallerpc ())
248+ }
249+ ```
250+
150251### select receive
151252
152253``` go
153254// select {
154255// case <-ch: ==> 这时候就会调用 selectrecv
155256// case ,ok <- ch: 也可以这样写
156257// 在 ch 被关闭时,这个 case 每次都可能被轮询到
157- // }
258+ // }
158259func selectrecv (sel *hselect , c *hchan , elem unsafe .Pointer , received *bool ) {
159260 pc := getcallerpc ()
160261 i := sel.ncase
@@ -179,6 +280,55 @@ func selectrecv(sel *hselect, c *hchan, elem unsafe.Pointer, received *bool) {
179280
180281```
181282
283+ ``` go
284+ // compiler implements
285+ //
286+ // select {
287+ // case v = <-c:
288+ // ... foo
289+ // default:
290+ // ... bar
291+ // }
292+ //
293+ // as
294+ //
295+ // if selectnbrecv(&v, c) {
296+ // ... foo
297+ // } else {
298+ // ... bar
299+ // }
300+ //
301+ func selectnbrecv (elem unsafe .Pointer , c *hchan ) (selected bool ) {
302+ selected, _ = chanrecv (c, elem, false )
303+ return
304+ }
305+ ```
306+
307+ ``` go
308+ // compiler implements
309+ //
310+ // select {
311+ // case v, ok = <-c:
312+ // ... foo
313+ // default:
314+ // ... bar
315+ // }
316+ //
317+ // as
318+ //
319+ // if c != nil && selectnbrecv2(&v, &ok, c) {
320+ // ... foo
321+ // } else {
322+ // ... bar
323+ // }
324+ //
325+ func selectnbrecv2 (elem unsafe .Pointer , received *bool , c *hchan ) (selected bool ) {
326+ // TODO(khr): just return 2 values from this function, now that it is in Go.
327+ selected, *received = chanrecv (c, elem, false )
328+ return
329+ }
330+ ```
331+
182332### select default
183333
184334``` go
0 commit comments