• 文件位置:go1.19.3/src/runtime/select.go。
  • select 的使用文档参考 select(使用)

Constants

const debugSelect = false // 调试模式

Variables

1
2
3
4
5
var (
    // 函数chansend的PC入口
    chansendpc = abi.FuncPCABIInternal(chansend)
    chanrecvpc = abi.FuncPCABIInternal(chanrecv)
)

type scase struct

  1. Select case 描述符。编译器已知。
  2. 这里修改必须去 src/cmd/compile/internal/walk/select.go’s scasetype。
  3. scase 结构是一个 case 的 channel 结构。
1
2
3
4
5
6
7
// Select case descriptor.
// Known to compiler.
// Changes here must also be made in src/cmd/compile/internal/walk/select.go's scasetype.
type scase struct {
    c    *hchan         // chan
    elem unsafe.Pointer // data element
}

select关键字

  1. selectgo 实现了 select 语句。
  2. cas0 指向类型为 [ncases]scase 的数组,order0 指向类型为 [2*ncases]uint16 的数组,其中 ncases 必须 <= 65536。
  3. 两者都存在于 goroutine 的栈中(不管 selectgo 中的任何转义)。
  4. 对于 race 的构建,pc0 指向一个类型为 [ncases]uintptr 的数组(也在这个栈上);对于其他构建,它被设置为nil。
  5. selectgo 返回所选分支的索引,它与各自的 select{recv,send,default} 调用的顺序位置匹配。
  6. 此外,如果选择的 scase 是一个 receive 接收类型操作,它会报告是否收到了值。
  7. 参数:
    1. cas0 *scase:指向一个数组,数组里装的是select中所有的case分支,按照send在前recv在后的顺序。不包含default分支。
    2. order0 *uint16:指向一个大小等于case分支数量两倍的uint16数组(2*(nsends+nrecvs)),实际上是作为两个大小相等的数组来用的。前一个用来对所有case中channel的轮询进行乱序,后一个用来对所有case中channel的加锁操作进行排序。
    3. pc0 *uintptr:race 相关。
    4. nsends int:send 操作的分支数量。
    5. nrecvs int:recv 操作的分支数量。
    6. block bool:表示是否想要阻塞等待。有default分支的不阻塞,反之则会阻塞。
  8. 返回值:
    1. int:表示最终哪个case分支被执行了,对应case0数组的下标。如果因为不想阻塞而返回,则这个值为-1。
    2. bool:表示在对应的case分支执行的是recv操作时,用来表示实际接收到一个值,而不是因为通道关闭得到的零值。
  9. selectgo() 函数流程:
    1. 先随机打乱cas0集用于轮询遍历。在按照channel地址升序排序用于全部的channel加锁和解锁。
    2. all channel lock后遍历打乱的case集检查是否能立即完成,如果可以则交换数据后全部解锁。否则判断block是否需要阻塞。
    3. 全部需要阻塞时,将当前goroutine封装成sudog形成一个链表挂在所有的channel上去等待。
    4. 当再次触发时,先获取清all channel lock,除挂在这些上面的sudog,解锁后返回。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
// selectgo implements the select statement.
//
// cas0 points to an array of type [ncases]scase, and order0 points to
// an array of type [2*ncases]uint16 where ncases must be <= 65536.
// Both reside on the goroutine's stack (regardless of any escaping in
// selectgo).
//
// For race detector builds, pc0 points to an array of type
// [ncases]uintptr (also on the stack); for other builds, it's set to
// nil.
//
// selectgo returns the index of the chosen scase, which matches the
// ordinal position of its respective select{recv,send,default} call.
// Also, if the chosen scase was a receive operation, it reports whether
// a value was received.
func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, block bool) (int, bool) {
    if debugSelect {
        print("select: cas0=", cas0, "\n")
    }

    // NOTE: In order to maintain a lean stack size, the number of scases
    // is capped at 65536.
    cas1 := (*[1 << 16]scase)(unsafe.Pointer(cas0))
    order1 := (*[1 << 17]uint16)(unsafe.Pointer(order0))

    // send + recv 数量和
    ncases := nsends + nrecvs
    // cases 集,按照 send + recv 顺序组成的
    scases := cas1[:ncases:ncases] // select case集
    // 轮询集,记录的是scases的下标,现在是空的。后面乱序填入
    pollorder := order1[:ncases:ncases] 
    // lockorder 用于按channel地址升序排序,所以方便Lock channel的作用。
    lockorder := order1[ncases:][:ncases:ncases] // lock集,现在是空的
    // NOTE: pollorder/lockorder's underlying array was not zero-initialized by compiler.

    // Even when raceenabled is true, there might be select
    // statements in packages compiled without -race (e.g.,
    // ensureSigM in runtime/signal_unix.go).
    var pcs []uintptr
    if raceenabled && pc0 != nil {
        pc1 := (*[1 << 16]uintptr)(unsafe.Pointer(pc0))
        pcs = pc1[:ncases:ncases]
    }
    casePC := func(casi int) uintptr {
        if pcs == nil {
            return 0
        }
        return pcs[casi]
    }

    var t0 int64
    if blockprofilerate > 0 {
        t0 = cputicks()
    }

    // The compiler rewrites selects that statically have
    // only 0 or 1 cases plus default into simpler constructs.
    // The only way we can end up with such small sel.ncase
    // values here is for a larger select in which most channels
    // have been nilled out. The general code handles those
    // cases correctly, and they are rare enough not to bother
    // optimizing (and needing to test).

    // 1) 乱序 pollorder,为后面select的随机性左准备

    // generate permuted order
    //
    // 生成排列的 order。
    norder := 0 // 有效的数量
    // 随机打乱 scases 并把打乱结果下标存入 pollorder 中。
    // 参看下面的【图一】
    for i := range scases {
        cas := &scases[i]

        // Omit cases without channels from the poll and lock orders.
        // 
        // 从 poll 和 lock orders 中省略没有 channels 的情况。 
        // nil 的 channel 会被丢弃。
        if cas.c == nil {
            cas.elem = nil // allow GC
            continue
        }
        // 生成随机数 [0, norder + 1]
        j := fastrandn(uint32(norder + 1))
        pollorder[norder] = pollorder[j]
        pollorder[j] = uint16(i)
        norder++
    }
    pollorder = pollorder[:norder] // 此时pollorder是打乱的轮询集
    lockorder = lockorder[:norder]

    // 2) lockorder 按 channel 地址排序,为了后面 all lock 准备
    // 按照地址升序排序有助于判断同一个channel在多个 case 中的情况

    // sort the cases by Hchan address to get the locking order.
    // simple heap sort, to guarantee n log n time and constant stack footprint.
    //
    // 将cases按照Hchan地址排序得到 locking order。
    // 简单的堆排序,保证 n log n 时间和恒定的栈占用。
    // 参看下面的【图一】
    for i := range lockorder { // 按照channel地址排序并记录在lockorder中升序。
        j := i
        // Start with the pollorder to permute cases on the same channel.
        c := scases[pollorder[i]].c
        // 按照 channel 的地址排序
        for j > 0 && scases[lockorder[(j-1)/2]].c.sortkey() < c.sortkey() {
            k := (j - 1) / 2
            lockorder[j] = lockorder[k]
            j = k
        }
        lockorder[j] = pollorder[i]
    }
    for i := len(lockorder) - 1; i >= 0; i-- {
        o := lockorder[i]
        c := scases[o].c
        lockorder[i] = lockorder[0]
        j := 0
        for {
            k := j*2 + 1
            if k >= i {
                break
            }
            if k+1 < i && scases[lockorder[k]].c.sortkey() < scases[lockorder[k+1]].c.sortkey() {
                k++
            }
            if c.sortkey() < scases[lockorder[k]].c.sortkey() {
                lockorder[j] = lockorder[k]
                j = k
                continue
            }
            break
        }
        lockorder[j] = o
    }

    if debugSelect {
        for i := 0; i+1 < len(lockorder); i++ {
            if scases[lockorder[i]].c.sortkey() > scases[lockorder[i+1]].c.sortkey() {
                print("i=", i, " x=", lockorder[i], " y=", lockorder[i+1], "\n")
                throw("select: broken sort")
            }
        }
    }

    // 3) 所有的 send、recv 获取 lock

    // lock all the channels involved in the select
    // 
    // 锁定select中涉及的所有channels。
    sellock(scases, lockorder)  // lock channel

    var (
        gp     *g               // 找到的goroutine
        sg     *sudog
        c      *hchan           // channel
        k      *scase
        sglist *sudog
        sgnext *sudog
        qp     unsafe.Pointer
        nextp  **sudog
    )

    // pass 1 - look for something already waiting
    // pass 1 - 寻找已经在等待的
    var casi int
    var cas *scase	
    var caseSuccess bool
    var caseReleaseTime int64 = -1
    var recvOK bool
    // 轮序 order
    for _, casei := range pollorder {
        casi = int(casei)   // 选中下标
        cas = &scases[casi]
        c = cas.c           // channel

        // recv 操作
        if casi >= nsends { // 注意 recv 先判断的能否成功,再判断的 close
            sg = c.sendq.dequeue() // sendq中寻找
            if sg != nil {
                goto recv          // 找到,去recv
            }
            // send buf 中有数据,去bufrecv
            if c.qcount > 0 {
                goto bufrecv
            }
            // 当前recv操作没完成,close关闭了,去rclose
            if c.closed != 0 {
                goto rclose
            }
        } else { // send 操作。注意 send 先判断的 close,再判断能否成功
            if raceenabled {
                racereadpc(c.raceaddr(), casePC(casi), chansendpc)
            }
            // channel 已经关闭
            if c.closed != 0 {
                goto sclose
            }
            sg = c.recvq.dequeue() // recvq中寻找
            if sg != nil {
                goto send
            }
            // buf 中还有容量
            if c.qcount < c.dataqsiz {
                goto bufsend
            }
        }
    }
    
    // 4) 以上都没有能立即完成时。可以走default分支不?

    // block 为false时存在default分支,不想阻塞。
    // block 为true时不存在default分支,阻塞。
    if !block {
        selunlock(scases, lockorder) // all channel unlock
        casi = -1 // -1 没找到
        goto retc
    }

    // 5) 把当前 goroutine 挂在所有 channel 等待。

    // pass 2 - enqueue on all chans
    // pass 2 - 对所有chan进行排队
    gp = getg() // g
    if gp.waiting != nil {
        throw("gp.waiting != nil")
    }
    nextp = &gp.waiting
    // 遍历 lockorder,这里当前goroutine被加入到多个channel中
    for _, casei := range lockorder {
        casi = int(casei)
        cas = &scases[casi]
        c = cas.c // channel
        sg := acquireSudog() // 寻找一个*sudog
        sg.g = gp // 记录当前goroutine
        sg.isSelect = true // 标记是在select
        // No stack splits between assigning elem and enqueuing
        // sg on gp.waiting where copystack can find it.
        sg.elem = cas.elem
        sg.releasetime = 0
        if t0 != 0 {
            sg.releasetime = -1
        }
        sg.c = c
        // Construct waiting list in lock order.
        // 所有的 sudog 组成个链表,有两个作用:【参看下面图片】
        //   1. 后续selparkcommit函数通过sudog.c解锁所有的 channel。
        //   2. 就绪后用于判断是那个 case 就绪了。
        *nextp = sg
        // 通过 waitlink 链接
        nextp = &sg.waitlink

        // 加入队列中
        if casi < nsends {
            c.sendq.enqueue(sg)
        } else {
            c.recvq.enqueue(sg)
        }
    }

    // wait for someone to wake us up
    // 等着有人叫醒我们
    gp.param = nil // 在被唤醒时会给param参数上赋值
    // Signal to anyone trying to shrink our stack that we're about
    // to park on a channel. The window between when this G's status
    // changes and when we set gp.activeStackChans is not safe for
    // stack shrinking.
    // 参看 channel 文档
    atomic.Store8(&gp.parkingOnChan, 1) // parkingOnChan = 1
    // 挂起,进入调度循环。
    gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1)
    
    // 6) 再次被唤醒时,... ...
    
    gp.activeStackChans = false

    // 全部channels锁住
    sellock(scases, lockorder) // all channel lock

    // 标记为0,表示select已完成。此时所有的channels都锁住了。
    gp.selectDone = 0
    // 被唤醒的goroutine的*sudog放在param上
    // 直接获取使用,关于 gp.param 的部分赋值代码在 chan send和recv上
    sg = (*sudog)(gp.param)
    gp.param = nil

    // pass 3 - dequeue from unsuccessful chans
    // otherwise they stack up on quiet channels
    // record the successful case, if any.
    // We singly-linked up the SudoGs in lock order.
    casi = -1
    cas = nil
    caseSuccess = false
    // 在 waiting 上等待着很多goroutine
    sglist = gp.waiting
    // Clear all elem before unlinking from gp.waiting.
    // 在从gp.waiting断开连接前清除所有elem。
    for sg1 := gp.waiting; sg1 != nil; sg1 = sg1.waitlink {
        sg1.isSelect = false
        sg1.elem = nil
        sg1.c = nil
    }
    gp.waiting = nil
    // 7) 遍历 lockorder,寻找是哪个case就绪了
    for _, casei := range lockorder {
        k = &scases[casei]
        if sg == sglist { // 找到就绪的 case
            // sg has already been dequeued by the G that woke us up.
            casi = int(casei) // 找到就绪的 case
            cas = k
            caseSuccess = sglist.success
            if sglist.releasetime > 0 {
                caseReleaseTime = sglist.releasetime
            }
        } else { // 移除没就绪的
            c = k.c
            // 从 channel 中移除 sglist
            // 因为其他 channel 中还挂起的呢,要移除
            if int(casei) < nsends {
                c.sendq.dequeueSudoG(sglist) 
            } else {
                c.recvq.dequeueSudoG(sglist) 
            }
        }
        sgnext = sglist.waitlink // 换下一个
        sglist.waitlink = nil
        releaseSudog(sglist) // 回收*sudog
        sglist = sgnext
    }

    if cas == nil {
        throw("selectgo: bad wakeup")
    }

    c = cas.c // channel

    if debugSelect {
        print("wait-return: cas0=", cas0, " c=", c, " cas=", cas, " send=", casi < nsends, "\n")
    }

    // send 操作
    if casi < nsends {
        // caseSuccess = true成功,false由于closed函数关闭触发。
        if !caseSuccess {
            goto sclose
        }
    } else { // recv 操作
        recvOK = caseSuccess
    }

    if raceenabled {
        if casi < nsends {
            raceReadObjectPC(c.elemtype, cas.elem, casePC(casi), chansendpc)
        } else if cas.elem != nil {
            raceWriteObjectPC(c.elemtype, cas.elem, casePC(casi), chanrecvpc)
        }
    }
    if msanenabled {
        if casi < nsends {
            msanread(cas.elem, c.elemtype.size)
        } else if cas.elem != nil {
            msanwrite(cas.elem, c.elemtype.size)
        }
    }
    if asanenabled {
        if casi < nsends {
            asanread(cas.elem, c.elemtype.size)
        } else if cas.elem != nil {
            asanwrite(cas.elem, c.elemtype.size)
        }
    }

    selunlock(scases, lockorder) // all channel unlock
    goto retc

bufrecv: // buf 中有数据, recv操作【ep <- c】
    // can receive from buffer
    if raceenabled {
        if cas.elem != nil {
            raceWriteObjectPC(c.elemtype, cas.elem, casePC(casi), chanrecvpc)
        }
        racenotify(c, c.recvx, nil)
    }
    if msanenabled && cas.elem != nil {
        msanwrite(cas.elem, c.elemtype.size)
    }
    if asanenabled && cas.elem != nil {
        asanwrite(cas.elem, c.elemtype.size)
    }
    recvOK = true // 数据交换成功标识
    qp = chanbuf(c, c.recvx)
    if cas.elem != nil {
        // 数据给到等待的变量
        typedmemmove(c.elemtype, cas.elem, qp)
    }
    typedmemclr(c.elemtype, qp)
    // 处理 buf 的下标
    c.recvx++
    if c.recvx == c.dataqsiz {
        c.recvx = 0
    }
    c.qcount--
    selunlock(scases, lockorder) // all channel unlock
    goto retc

bufsend: // buf 中还有容量,send操作【c <- ep】
    // can send to buffer
    if raceenabled {
        racenotify(c, c.sendx, nil)
        raceReadObjectPC(c.elemtype, cas.elem, casePC(casi), chansendpc)
    }
    if msanenabled {
        msanread(cas.elem, c.elemtype.size)
    }
    if asanenabled {
        asanread(cas.elem, c.elemtype.size)
    }
    // 数据迁移
    typedmemmove(c.elemtype, chanbuf(c, c.sendx), cas.elem)
    // 处理下标
    c.sendx++
    if c.sendx == c.dataqsiz {
        c.sendx = 0
    }
    c.qcount++
    selunlock(scases, lockorder)
    goto retc

recv: // sendq 中获取到 sg goroutine
    // can receive from sleeping sender (sg)
    // 数据在挂起的 send 的 goroutine 里面,调用recv取交换数据。
    recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2) // 恢复并解锁all channel
    if debugSelect {
        print("syncrecv: cas0=", cas0, " c=", c, "\n")
    }
    recvOK = true // recv操作成功
    goto retc

rclose: // channel 已经关闭, recv 操作时
    // read at end of closed channel
    selunlock(scases, lockorder) // all channel unlock
    recvOK = false // channel 关闭的 recv
    if cas.elem != nil {
        // 接受值设置成默认零值,因为close了
        typedmemclr(c.elemtype, cas.elem)
    }
    if raceenabled {
        raceacquire(c.raceaddr())
    }
    goto retc

send: // recvq 中有等待 sg goroutine。
    // can send to a sleeping receiver (sg)
    if raceenabled {
        raceReadObjectPC(c.elemtype, cas.elem, casePC(casi), chansendpc)
    }
    if msanenabled {
        msanread(cas.elem, c.elemtype.size)
    }
    if asanenabled {
        asanread(cas.elem, c.elemtype.size)
    }
    // 数据在挂起的 recv 的 goroutine 里面,调用 send 去交换数据。
    send(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
    if debugSelect {
        print("syncsend: cas0=", cas0, " c=", c, "\n")
    }
    goto retc

retc:
    if caseReleaseTime > 0 {
        blockevent(caseReleaseTime-t0, 1)
    }
    return casi, recvOK

sclose: // channel 已经关闭,send 操作时
    // send on closed channel
    selunlock(scases, lockorder)
    panic(plainError("send on closed channel"))
}

sellock()

  1. 所有 case 的 send、recv 操作的获取 hmap.lock 互斥锁。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func sellock(scases []scase, lockorder []uint16) {
    var c *hchan
    // 按照lockorder遍历,因为是升序,所以相同的channel在一起
    for _, o := range lockorder {
        c0 := scases[o].c
        if c0 != c { // 如果是同一个 channel 跳过它
            c = c0
            lock(&c.lock) // mutex lock
        }
    }
}

selunlock()

  1. 所有 case 的 send、recv 操作的释放 hmap.lock 互斥锁。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func selunlock(scases []scase, lockorder []uint16) {
    // We must be very careful here to not touch sel after we have unlocked
    // the last lock, because sel can be freed right after the last unlock.
    // Consider the following situation.
    // First M calls runtime·park() in runtime·selectgo() passing the sel.
    // Once runtime·park() has unlocked the last lock, another M makes
    // the G that calls select runnable again and schedules it for execution.
    // When the G runs on another M, it locks all the locks and frees sel.
    // Now if the first M touches sel, it will access freed memory.
    for i := len(lockorder) - 1; i >= 0; i-- {
        c := scases[lockorder[i]].c
        // 如果是同一个 channel 跳过它
        if i > 0 && c == scases[lockorder[i-1]].c {
            continue // will unlock it on the next iteration
        }
        unlock(&c.lock)
    }
}

selparkcommit()

  1. 当前 select 没有就绪 case 导致当前 goroutine 被挂起前执行的函数。
func selparkcommit(gp *g, _ unsafe.Pointer) bool {
    // There are unlocked sudogs that point into gp's stack. Stack
    // copying must lock the channels of those sudogs.
    // Set activeStackChans here instead of before we try parking
    // because we could self-deadlock in stack growth on a
    // channel lock.
    gp.activeStackChans = true // 参看channel文档
    // Mark that it's safe for stack shrinking to occur now,
    // because any thread acquiring this G's stack for shrinking
    // is guaranteed to observe activeStackChans after this store.
    atomic.Store8(&gp.parkingOnChan, 0) // 参看channel文档
    // Make sure we unlock after setting activeStackChans and
    // unsetting parkingOnChan. The moment we unlock any of the
    // channel locks we risk gp getting readied by a channel operation
    // and so gp could continue running before everything before the
    // unlock is visible (even to gp itself).

    // This must not access gp's stack (see gopark). In
    // particular, it must not access the *hselect. That's okay,
    // because by the time this is called, gp.waiting has all
    // channels in lock order.
    var lastc *hchan
    // 依次channel解锁
    for sg := gp.waiting; sg != nil; sg = sg.waitlink {
        if sg.c != lastc && lastc != nil {
            // As soon as we unlock the channel, fields in
            // any sudog with that channel may change,
            // including c and waitlink. Since multiple
            // sudogs may have the same channel, we unlock
            // only after we've passed the last instance
            // of a channel.
            unlock(&lastc.lock)
        }
        lastc = sg.c
    }
    if lastc != nil {
        unlock(&lastc.lock)
    }
    return true
}

selectgo 参数组成

以下都是介绍selectgo()参数的组成,对于理解goselect增加帮助。

type runtimeSelect struct

  1. runtimeSelect 是传递给 rselect 的一个 case。
  2. 必须匹配 ../reflect/value.go:/runtimeSelect。
  3. 该结构理解为select case中的所有case组成的集合。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// A runtimeSelect is a single case passed to rselect.
// This must match ../reflect/value.go:/runtimeSelect
type runtimeSelect struct {
    // channel 类型,包含:send、recv、default 三种
    dir selectDir
    typ unsafe.Pointer // channel type (not used here)
    // 当前 case 操作所属的 channel
    ch  *hchan         // channel
    // send 或 recv 操作时数据的地址
    val unsafe.Pointer // ptr to data (SendDir) or ptr to receive buffer (RecvDir)
}

type selectDir int

1
2
3
4
5
6
7
8
9
// These values must match ../reflect/value.go:/SelectDir.
type selectDir int

const (
    _             selectDir = iota
    selectSend      // case Chan <- Send
    selectRecv      // case <-Chan:
    selectDefault   // default
)

reflect_rselect()

  1. reflect_rselectselect关键字相关代码。
  2. 对于代码select {},当前goroutine会丢失再也不会被调度。
  3. 参数cases []runtimeSelectselect的所有分支数据信息。
  4. 返回值int:返回选中的分支下标。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//go:linkname reflect_rselect reflect.rselect
func reflect_rselect(cases []runtimeSelect) (int, bool) {
    // 没有 case 分支
    // 对于 `select {}` 当前goroutine会直接丢失
    if len(cases) == 0 {
        block() // gopark
    }
    
    // len(cases) = send + recv + default
    sel := make([]scase, len(cases)) // scase 集
    orig := make([]int, len(cases))  // sel下标和cases的对应关系
    // 临时变量用于保存 send 和 recv
    nsends, nrecvs := 0, 0 // send And recv
    // 标记select中默认default分支下标
    dflt := -1 // 默认值-1表示没有默认default分支
    // 遍历select的所有case,包括default分支
    // 使用 dflt 标记 default 分支的下标
    // sel 整理存储为 send + recv
    // orig 存储sel和cases的映射关系【参看上面"图一"】
    for i, rc := range cases {
        // 临时变量,用于计算当前case应该放入的位置
        var j int 
        switch rc.dir {
        // default分支
        case selectDefault:
            dflt = i
            continue
        // send 分支:c <- ep
        case selectSend:
            // 从前向后,依次放入
            j = nsends 
            nsends++
        // recv 分支:<- c
        case selectRecv:
            // 从后向前,依次放入
            nrecvs++
            j = len(cases) - nrecvs 
        }
        
        // 保存对应关系
        sel[j] = scase{c: rc.ch, elem: rc.val}
        orig[j] = i
    }
    
    // for range 完后,sel 保存 send + recv
    // orig 保存 sel 和 cases 对应关系

    // Only a default case.
    // 
    // 仅仅只有一个 default case。
    // 因为如果一个分支都没有,最前面就返回了
    // select {default:}
    if nsends+nrecvs == 0 {
        return dflt, false
    }

    // Compact sel and orig if necessary.
    // 
    // 如果有必要使sel 和 orig紧凑。【参看上面"图二"】
    if nsends+nrecvs < len(cases) { // 存在defult时
        copy(sel[nsends:], sel[len(cases)-nrecvs:])
        copy(orig[nsends:], orig[len(cases)-nrecvs:])
    }

    // order 长度是 2*(nsends+nrecvs)
    order := make([]uint16, 2*(nsends+nrecvs))
    var pc0 *uintptr
    if raceenabled {
        pcs := make([]uintptr, nsends+nrecvs)
        for i := range pcs {
            selectsetpc(&pcs[i])
        }
        pc0 = &pcs[0]
    }

    // &sel[0]:是send+recv
    // &order[0]:是空的,dflt true.不存在defalut分支 false.存在default分支
    // chosen:表示选择的case下标,如果为-1时为默认default分支
    // recvOK:表示数据是否交换成功,true.成功 false.失败
    // 从case分支中选择一个就绪的channel
    chosen, recvOK := selectgo(&sel[0], &order[0], pc0, nsends, nrecvs, dflt == -1)

    // Translate chosen back to caller's ordering.
    //
    // chosen < 0 选中默认分支
    if chosen < 0 {
        chosen = dflt // 默认default
    } else {
        chosen = orig[chosen] // case
    }
    return chosen, recvOK
}

block()

1
2
3
4
func block() {
    // 注意:这里第一和第二个参数都为nil,表明当前goroutine被丢弃了。
    gopark(nil, nil, waitReasonSelectNoCases, traceEvGoStop, 1) // forever
}

汇编验证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    select {
    case v := <-ch1:
        println(v)
    case ch2 <- 10:
    default:
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
TEXT main.main(SB) /mnt/GoProject/small/tt1.go
func main() {
  0x459580      4c8d6424c8          LEAQ -0x38(SP), R12
  0x459585      4d3b6610            CMPQ 0x10(R14), R12
  0x459589      0f8632010000        JBE 0x4596c1
  0x45958f      4881ecb8000000      SUBQ $0xb8, SP
  0x459596      4889ac24b0000000    MOVQ BP, 0xb0(SP)
  0x45959e      488dac24b0000000    LEAQ 0xb0(SP), BP
    ch1 := make(chan int)
  0x4595a6      488d0513490000      LEAQ 0x4913(IP), AX
  0x4595ad      31db                XORL BX, BX
  0x4595af      e8eca6faff          CALL runtime.makechan(SB)
  0x4595b4      4889442468          MOVQ AX, 0x68(SP)
    ch2 := make(chan int)
  0x4595b9      488d0500490000      LEAQ 0x4900(IP), AX
  0x4595c0      31db                XORL BX, BX
  0x4595c2      e8d9a6faff          CALL runtime.makechan(SB)
  0x4595c7      4889442460          MOVQ AX, 0x60(SP)
    case v := <-ch1:
  0x4595cc      488b4c2468          MOVQ 0x68(SP), CX   #CX=&ch1
  0x4595d1      48894c2478          MOVQ CX, 0x78(SP)
    case ch2 <- 10:
  0x4595d6      488b4c2460          MOVQ 0x60(SP), CX
  0x4595db      48894c2470          MOVQ CX, 0x70(SP)
  0x4595e0      48c74424500a000000  MOVQ $0xa, 0x50(SP)
    select {
  0x4595e9      440f11bc2490000000  MOVUPS X15, 0x90(SP)
  0x4595f2      440f11bc24a0000000  MOVUPS X15, 0xa0(SP)
    case v := <-ch1:
  0x4595fb      488b4c2478          MOVQ 0x78(SP), CX
  0x459600      48898c24a0000000    MOVQ CX, 0xa0(SP)
  0x459608      488d4c2458          LEAQ 0x58(SP), CX
  0x45960d      48898c24a8000000    MOVQ CX, 0xa8(SP)
    case ch2 <- 10:
  0x459615      488b4c2470          MOVQ 0x70(SP), CX
  0x45961a      48898c2490000000    MOVQ CX, 0x90(SP)
  0x459622      488d4c2450          LEAQ 0x50(SP), CX
  0x459627      48898c2498000000    MOVQ CX, 0x98(SP)
    select {
  0x45962f      488d8c2490000000    LEAQ 0x90(SP), CX
  0x459637      48898c2488000000    MOVQ CX, 0x88(SP)
  0x45963f      488d5c2448          LEAQ 0x48(SP), BX
  0x459644      48899c2480000000    MOVQ BX, 0x80(SP)
  0x45964c      488b842488000000    MOVQ 0x88(SP), AX
  0x459654      31c9                XORL CX, CX
  0x459656      bf01000000          MOVL $0x1, DI
  0x45965b      4889fe              MOVQ DI, SI
  0x45965e      4531c0              XORL R8, R8
  0x459661      e89a57feff          CALL runtime.selectgo(SB)
  0x459666      4889442440          MOVQ AX, 0x40(SP)
  0x45966b      885c2437            MOVB BL, 0x37(SP)
    default:
  0x45966f      48837c244000        CMPQ $0x0, 0x40(SP)
  0x459675      7c02                JL 0x459679
  0x459677      eb02                JMP 0x45967b
  0x459679      eb36                JMP 0x4596b1
    case ch2 <- 10:
  0x45967b      48837c244000        CMPQ $0x0, 0x40(SP)
  0x459681      7402                JE 0x459685
  0x459683      eb02                JMP 0x459687
  0x459685      eb2a                JMP 0x4596b1
    case v := <-ch1:
  0x459687      488b442458          MOVQ 0x58(SP), AX
  0x45968c      4889442438          MOVQ AX, 0x38(SP)
        println(v)
  0x459691      e86a5cfdff          CALL runtime.printlock(SB)
  0x459696      488b442438          MOVQ 0x38(SP), AX
  0x45969b      0f1f440000          NOPL 0(AX)(AX*1)
  0x4596a0      e85b63fdff          CALL runtime.printint(SB)
  0x4596a5      e8b65efdff          CALL runtime.printnl(SB)
  0x4596aa      e8d15cfdff          CALL runtime.printunlock(SB)
    case v := <-ch1:
  0x4596af      eb00                JMP 0x4596b1
}
  0x4596b1      488bac24b0000000    MOVQ 0xb0(SP), BP
  0x4596b9      4881c4b8000000      ADDQ $0xb8, SP
  0x4596c0      c3                  RET
func main() {
  0x4596c1      e8dacbffff          CALL runtime.morestack_noctxt.abi0(SB)
  0x4596c6      e9b5feffff          JMP main.main(SB)

总结

  1. select {} 会导致当前 goroutine 直接丢弃,再也不会被调用。
  2. select {default:} 直接跳过什么都不会做。
  3. select case 中存在 channel 为 nil,该 case 会被丢弃。
  4. select 中也一样,向关闭的channel,send会panic,recv会得到默认的零值。
  5. select 中,向 nil 的 channel,send 或 recv 不会 panic(channel会被丢弃)。而没在 select 中全部都会 panic。
  6. 注意:在 select 中 close 的 channel 情况:
    1. recv 先判断的能否成功,再判断的 close。
    2. send 先判断的 close,再判断能否成功。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
    "fmt"
)

func main() {
    c := make(chan int, 2)
    c <- 1
    close(c)

    select {
    case v, ok := <-c:
        fmt.Printf("received %d, ok = %v\n", v, ok)
    default:
        fmt.Printf("default")
    }

    // Output:
    // received 1, ok = true
}