<span style="color:#c0c0c0"> </span><span style="color:#000080font-weight:600">var</span><span style="color:#c0c0c0"></span>sliceArray<span style="color:#c0c0c0"></span><span style="color:#000000">[</span><span style="color:#800080">10</span><span style="color:#000000">]</span><span style="color:#000080">int</span><span style="color:#c0c0c0"></span><span style="color:#000000">=</span><span style="color:#c0c0c0"></span><span style="color:#000000">[</span><span style="color:#800080">10</span><span style="color:#000000">]</span><span style="color:#000080">int</span><span style="color:#000000">{</span><span style="color:#800080">0</span><span style="color:#000000">,</span><span style="color:#c0c0c0"></span><span style="color:#800080">1</span><span style="color:#000000">,</span><span style="color:#c0c0c0"></span><span style="color:#800080">2</span><span style="color:#000000">,</span><span style="color:#c0c0c0"></span><span style="color:#800080">3</span><span style="color:#000000">,</span><span style="color:#c0c0c0"></span><span style="color:#800080">4</span><span style="color:#000000">,</span><span style="color:#c0c0c0"></span><span style="color:#800080">5</span><span style="color:#000000">,</span><span style="color:#c0c0c0"></span><span style="color:#800080">6</span><span style="color:#000000">,</span><span style="color:#c0c0c0"></span><span style="color:#800080">7</span><span style="color:#000000">,</span><span style="color:#c0c0c0"></span><span style="color:#800080">8</span><span style="color:#000000">,</span><span style="color:#c0c0c0"></span><span style="color:#800080">9</span><span style="color:#000000">}</span>
//指定 begin index 和end Index
// begin index 和end index 都指定的情况 包括 begin index, 不包括end index index 从0开始
var slice2 []int = sliceArray[5:9]
// slice3 和slice2 指向同一个底层的数组
var slice3 []int = sliceArray[:]
//输出结果 [5 6 7 8] 4 5
fmt.Println(slice2, len(slice2), cap(slice2))
//测试添加元素 and 扩容之后的数组操作情况
//通过切片直接操作数组的信息
slice2[0] = 100
sliceArray[6] = 66
//多个切片 操作底层数组
slice3[7] = 77
//输出结果 [100 66 77 8] 4 5 三个赋值 都影响了切片的底层数组
fmt.Println(slice2, len(slice2), cap(slice2))
//输出结果 [0 1 2 3 4 100 66 77 8 9] 10 10
fmt.Println(slice3, len(slice3), cap(slice3))
//输出结果 [0 1 2 3 4 100 66 77 8 9] 三个赋值 都影响了 原始数组
fmt.Println("sliceArray:", sliceArray)
//扩容
// 操作的还是 sliceArray
slice2 = append(slice2, 99)
//输出结果 [100 66 77 8 99] 5 5
fmt.Println(slice2, len(slice2), cap(slice2))
// 输出结果 sliceArray: [0 1 2 3 4 100 66 77 8 99]
fmt.Println("sliceArray:", sliceArray)
slice2 = append(slice2, 1000)
//输出结果 [100 66 77 8 99 1000] 6 10 此处切片已经扩容(两倍扩容), 并保留了原始的内容
fmt.Println(slice2, len(slice2), cap(slice2))
// 输出结果 sliceArray: [0 1 2 3 4 100 66 77 8 99] 原来的数组不再受到影响了
fmt.Println("sliceArray:", sliceArray)
//通过index操作 元素 判断扩容后的底层数组是否是部分会在原始的 数组上面
slice2[1] = 999
//输出结果 [100 999 77 8 99 1000] 6 10
fmt.Println(slice2, len(slice2), cap(slice2))
// 输出结果 sliceArray: [0 1 2 3 4 100 66 77 8 99] 说明是 重新找了一块内存, 和以前的数组完全没有关系
fmt.Println("sliceArray:", sliceArray)
Go 的select语句是一种仅能用于channl发送和接收消息的专用语句,此语句运行期间是阻塞的;当select中没有case语句的时候,会阻塞当前的groutine。所以,有人也会说select是用来阻塞监听goroutine的。还有人说:select是Golang在语言层面提供的I/O多路复用的机制,其专门用来检测多个channel是否准备完毕:可读或可写。
以上说法都正确。
我们来回顾一下是什么是 I/O多路复用 。
每来一个进程,都会建立连接,然后阻塞,直到接收到数据返回响应。
普通这种方式的缺点其实很明显:系统需要创建和维护额外的线程或进程。因为大多数时候,大部分阻塞的线程或进程是处于等待状态,只有少部分会接收并处理响应,而其余的都在等待。系统为此还需要多做很多额外的线程或者进程的管理工作。
为了解决图中这些多余的线程或者进程,于是有了"I/O多路复用"
每个线程或者进程都先到图中”装置“中注册,然后阻塞,然后只有一个线程在”运输“,当注册的线程或者进程准备好数据后,”装置“会根据注册的信息得到相应的数据。从始至终kernel只会使用图中这个黄黄的线程,无需再对额外的线程或者进程进行管理,提升了效率。
select的实现经历了多个版本的修改,当前版本为:1.11
select这个语句底层实现实际上主要由两部分组成: case语句 和 执行函数 。
源码地址为:/go/src/runtime/select.go
每个case语句,单独抽象出以下结构体:
结构体可以用下图表示:
然后执行select语句实际上就是调用 func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) 函数。
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) 函数参数:
selectgo 返回所选scase的索引(该索引与其各自的select {recv,send,default}调用的序号位置相匹配)。此外,如果选择的scase是接收操作(recv),则返回是否接收到值。
谁负责调用 func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) 函数呢?
在 /reflect/value.go 中有个 func rselect([]runtimeSelect) (chosen int, recvOK bool) 函数,此函数的实现在 /runtime/select.go 文件中的 func reflect_rselect(cases []runtimeSelect) (int, bool) 函数中:
那谁调用的 func rselect([]runtimeSelect) (chosen int, recvOK bool) 呢?
在 /refect/value.go 中,有一个 func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) 的函数,其调用了 rselect 函数,并将最终Go中select语句的返回值的返回。
以上这三个函数的调用栈按顺序如下:
这仨函数中无论是返回值还是参数都大同小异,可以简单粗暴的认为:函数参数传入的是case语句,返回值返回被选中的case语句。
那谁调用了 func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) 呢?
可以简单的认为是系统了。
来个简单的图:
前两个函数 Select 和 rselect 都是做了简单的初始化参数,调用下一个函数的操作。select真正的核心功能,是在最后一个函数 func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) 中实现的。
打乱传入的case结构体顺序
锁住其中的所有的channel
遍历所有的channel,查看其是否可读或者可写
如果其中的channel可读或者可写,则解锁所有channel,并返回对应的channel数据
假如没有channel可读或者可写,但是有default语句,则同上:返回default语句对应的scase并解锁所有的channel。
假如既没有channel可读或者可写,也没有default语句,则将当前运行的groutine阻塞,并加入到当前所有channel的等待队列中去。
然后解锁所有channel,等待被唤醒。
此时如果有个channel可读或者可写ready了,则唤醒,并再次加锁所有channel,
遍历所有channel找到那个对应的channel和G,唤醒G,并将没有成功的G从所有channel的等待队列中移除。
如果对应的scase值不为空,则返回需要的值,并解锁所有channel
如果对应的scase为空,则循环此过程。
在想想select和channel做了什么事儿,我觉得和多路复用是一回事儿