Skip to content

Commit 2d8424d

Browse files
committed
Merge branch 'master' of github.com:cch123/golang-notes
2 parents d16b44b + eeed172 commit 2d8424d

File tree

2 files changed

+152
-1
lines changed

2 files changed

+152
-1
lines changed

select.md

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Select
22

3+
select 的实现在目前 Go 的 master 分支已经有所修改,所以本文只针对 1.10 之前的版本。
4+
35
## 基本用法
46

57
非阻塞 select:
@@ -24,6 +26,82 @@ select {
2426
println("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+
// }
158259
func 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

select_new.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Select

0 commit comments

Comments
 (0)