GO 语言 切片和底层数组的关系

Python017

GO 语言 切片和底层数组的关系,第1张

//从数组中获取 切片

<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做了什么事儿,我觉得和多路复用是一回事儿