数据结构
数组和切片的区别
数组是由固定长度的同一类型元素组成的序列,可以通过显示声明长度或给定初值来初始化一个数组。数组长度是数组类型的一个组成部分。
Slice切片是由同一类型元素组成的变长序列,一般写作[]T。
数组和slice之间有着紧密的联系。一个slice是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用一个数组对象。一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠。
slice切片的初始化方式:
(1) 通过下标的方式获得数组或者切片的一部分:arr[0:3] or slice[0:3]
(2) 使用字面量初始化新的切片:slice := []int{1, 2, 3}
1,根据切片中的元素数量对底层数组的大小进行推断并创建一个数组;
2,将这些字面量元素存储到初始化的数组中;
3,创建一个同样指向 [3]int 类型的数组指针;
4,将静态存储区的数组 vstat 赋值给 vauto 指针所在的地址;
5,通过 [:] 操作获取一个底层使用 vauto 的切片;
(3) 使用关键字 make 创建切片:slice := make([]int, 10)
和数组不同的是,slice之间不能比较,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。
append原理和扩容机制
每次调用appendInt函数,必须先检测slice底层数组是否有足够的容量来保存新添加的元素。
如果有足够空间的话,直接扩展slice(依然在原有的底层数组之上),将新添加的y元素复制到新扩展的空间,并返回slice。
如果没有足够的增长空间的话,appendInt函数则会先分配一个足够大的slice用于保存新的结果,先将输入的x复制到新的空间,然后添加y元素。
copy函数的第一个参数是要复制的目标slice,第二个参数是源slice,目标和源的位置顺序和dst = src赋值语句是一致的。两个slice可以共享同一个底层数组,甚至有重叠也没有问题。若原slice容量小于1024就会将容量扩大为原来的两倍,否则扩大为原来的1.25倍,这样可以节约资源。
当且仅当待append的元素数量 + 当前长度 > cap时,slice需要扩容和数据拷贝,这种时候cap和pointer就会被修改。
go的map(数据结构)
golang中map的底层数据结构是一个叫hmap的结构体struct。
type hmap struct {
// map中存入元素的个数, golang中调用len(map)的时候直接返回该字段
count int
// 状态标记位,通过与定义的枚举值进行&操作可以判断当前是否处于这种状态
flags uint
B uint8 // 2^B 表示bucket的数量, B 表示取hash后多少位来做bucket的分组
noverflow uint16 // overflow bucket 的数量的近似数
hash0 uint32 // hash seed (hash 种子) 一般是一个素数
buckets unsafe.Pointer // 共有2^B个 bucket ,但是如果没有元素存入,这个字段可能为nil
oldbuckets unsafe.Pointer // 在扩容期间,将旧的bucket数组放在这里, 新buckets会是这个的两倍大
nevacuate uintptr // 表示已经完成扩容迁移的bucket的指针, 地址小于当前指针的bucket已经迁移完成
extra *mapextra // optional fields
}
hmap里的buckets指向一个bmap桶的数组,数组长度2的B次方,而一个桶里最多放入8个key-value键值对。
、
map的有序性
go的map(插入和查询)
go的map的插入和查询都首先需要做的一个工作就是,查询当前key/value应该存储/读取的位置。然后再赋值或取值。
go的map(扩容机制)
扩容触发条件
扩容的过程
这边文章写的较清晰:juejin.cn/post/710200…
go的map(协程安全)
首先golang中的map并不是并发安全的数据结构,golang提供了额外的sync.map并发安全的map。
什么样的场景会有并发安全问题呢?
关于如何实现并发安全的map,这篇文章挺好的 zhuanlan.zhihu.com/p/356739568 而且也有对sync.map的介绍,还挺清晰的
go的map(sync.map原理)
这篇简单清楚点 juejin.cn/post/684490…
Channel概念
channel不仅是go语言中重要的一个数据结构(像map一样),同样是goroutine之间的通信方式。Go语言鼓励的并发模型:通过通信的方式共享内存而不是通过共享内存的方式通信,简单说就是goroutine之间通过channel传递数据。
channel遵循先进先出原则,先从 Channel 读取数据的 Goroutine 会先接收到数据;先向 Channel 发送数据的 Goroutine 会得到先发送数据的权利。
channel的创建都会用到make关键字,ch := make(chan int, 10)。
发送数据:ch <- i主要调用runtime.chansend 函数,该函数的执行过程分成三种情况:发送直接接收、发送至缓冲区、发送阻塞。接收数据:i <- ch 或 i, ok <- ch,主要调用runtime.chanrecv函数,也是相应的三种情况。
type hchan struct {
qcount uint // Channel 中的元素个数;
dataqsiz uint // Channel 中的循环队列的长度;
buf unsafe.Pointer // Channel 的缓冲区数据指针;
elemsize uint16 // Channel 能够收发的元素大小
closed uint32 // 该Channel是否关闭
elemtype *_type // Channel 能够收发的元素类型
sendx uint // Channel 的发送操作处理到的位置
recvx uint // Channel 的接收操作处理到的位置
recvq waitq // 存储了当前 Channel 由于缓冲区空间不足而阻塞的接受Goroutine 列表
sendq waitq // 存储了当前 Channel 由于缓冲区空间不足而阻塞的发送 Goroutine 列表
lock mutex // 用于保护成员变量的互斥锁
}
各类channel
无缓存channel
单方向channel
带缓存的channel
并发
并发控制之Context
理解 context.Context 的使用方法和设计原理 — 多个 Goroutine 同时订阅 ctx.Done() 管道中的消息,一旦接收到取消信号就立刻停止当前正在执行的工作。
默认上下文
取消信号、传值方法等点。
并发控制之原语和锁
协程(基础概念)
进程是程序实体一次运行的过程,是操作系统资源分配的基础单元。
线程是轻量级进程,是操作系统任务执行的基本单元。同一进程下可以有多个线程,线程共享进行的内存空间,同时又有线程私有的少量内存空间。线程相对于进程切换开销更小、同一进程下线程通信更容易。
协程是用户态的线程。线程虽然是轻量级的,但在线程较多的情况下仍然有较大的切换开销和内存占用。而golang的协程在用户态切换,开销比线程更小,而且协程占用的内存也更少,线程占用内存至少在1MB,而协程占用的内存可以在几KB。
GMP模型
关于模型本身的内容可以看Xmind思维导图,总结的挺好的
G、M的数量,G、M各有集中状态,这些都要指导
调度器P的调度策略和调度流程
调度策略或者叫调度机制,也可以看思维导图,总结的真挺好
系统调用M会阻塞,而用户态阻塞是G阻塞而不是M。
特殊的M0和G0,与调度的生命周期相关。顺哥还是有东西的呀。
协程(进程间通信)
管道、共享内存、信号量P/V、信号等。
协程(网络服务)
这篇真的很厉害,主要是对于goroutine在网络编程、服务器这块与异步网络编程select()的对比,真的会填补知识空白 www.cnblogs.com/liang1101/p…
函数
闭包
golang的函数闭包指,在函数内部,引用函数局部变量的内部函数,这里的内部函数可以是内部实名函数、内部匿名函数、或一段lambda表达式。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
这个文章挺扫盲的 studygolang.com/articles/35…
defer就是golang函数闭包的一个应用!!
在函数里用go func启一个协程也是在用闭包啊!!c.biancheng.net/view/98.htm…
逃逸机制
这个博客挺通俗易懂的 geektutu.com/post/hpg-es…
前置扫盲
逃逸分析是什么
逃逸分析来看传值和传指针
defer
这篇感觉挺好的,基础应该有了 jincheng9.github.io/post/go-def…
- 被
defer的函数或方法什么时候执行? - 被
defer的函数或方法的参数的值是什么时候确定的? - 被
defer的函数或方法如果存在多级调用是什么机制?
panic和recover原理
panic 与 recover 是 Go 的两个内置函数,这两个内置函数用于处理 Go 运行时的错误,panic 用于主动抛出错误,recover 用来捕获 panic 抛出的错误。