GO进阶
2021-07-07 16:17:38 38 举报
AI智能生成
Go知识网络梳理
作者其他创作
大纲/内容
主线
go
内置函数
new(T)
分配一块内存,返回T类型指针
make(T)
初始化底层数据指针,返回T类型值
copy
命令行
go vet
代码错误检查
内置类型
uintptr
用于指针运算
不能存储在临时变量中
why:从GC的角度看,uintptr类型的临时变量只是一个无符号整数(十进制的地址),并不知道它是一个指针地址,当满足一定条件,这个临时变量将被回收,接下来的内存操作将不可预测
深挖:垃圾回收
int8,int16,int32(rune),int64
uint8(byte),uint16,uint32,uint64
bool
string
float32,float64
int,uint
complex64,complex128
数据结构
chan
从channel读数据
如果缓冲区中有数据,则从缓冲区取出数据,结束读取过程
如果缓冲区没数据,且等待发送队列sendq不为空,则从sendq中取出G,把G中数据读出,最后把G唤醒,结束读取过程
如果缓冲区没数据,且等待发送队列sendq为空,则将当前goroutine放入等待接收队列recvq,进入睡眠,等待被唤醒
向channel写数据
如果缓冲区有空余位置,则将数据写入缓冲区,结束写入过程
如果缓冲区没有空余位置,且等待接收队列recvq为空,则将当前goroutine放入sendq,进入睡眠,等待被唤醒
如果缓冲区没有空余位置,且等待接收队列recvq不为空,则从recvq取出G,将数据写入,最后唤醒G,结束写入过程
关闭channel
关闭channel时,将revcq中G全部唤醒,本该写入G的数据位置为nil,将sendq中G全部唤醒,但这些G会panic
panic情况
关闭值为nil的channel
关闭已经关闭的channel
向已关闭的channel写数据
注意
select的case读取channel的时候不会阻塞,这是由于case语句编译后读取channel会明确传入不阻塞参数,此时读不到数据不会将当前goroutine放入recvq而是直接返回
遍历channel时,若写数据的goroutine退出,则系统panic
slice
创建slice
cap为slice首元素到底层数组尾元素的长度
slice扩容
如果原slice容量小于1024,则容量扩大为原slice的2倍
如果原slice容量大于等于1024,则容量扩大为原slice的1.25倍
copy
切片copy,拷贝数量取两个切片长度最小值,不会发生扩容
map
bucket哈希桶
哈希冲突
当两个或以上数量的键哈希到同一bucket时,我们称之为哈希冲突
Go使用链地址法解决键冲突,由于每个bucket可以存放8个键值对,超过8个时,创建一个bucket,使用类似链表的方式将新bucket连起来,新bucket被称为overflow bucket
负载因子
键数量/bucket数量
负载因子过小,说明空间利用率低
负载因子过大,说明哈希冲突严重
扩容
扩容条件
负载因子大于6.5
overflow数量大于2^15
增量扩容
当负载因子过大时,就新建一个buckets,长度为原来的两倍,然后将旧bucket数据逐步搬迁到新bucket,每次访问map搬迁两个key
等量扩容
buckets数量不变,重新做一遍类似搬迁的动作,把松散的键值对重新排列一次,是bucket效率更高
查找
根据key计算哈希值
取哈希值低位与hmap.B取模,确定bucket位置
取哈希值高位在tophash数组中查询,若tophash[i]与哈希值相等,则去找到该bucket中key值进行比较,若bucket中没有该key,则依次查找overflow
如果当前处于搬迁状态,则优先从oldbuckets中查找
插入
查找key是否已经存在,若存在则直接更新,不存在则插入
struct
iota
iota表示const代码块的行索引
const常量的特点:第一行必须有表达式,后续如果没有表达式则继承上一行的表达式
string
string可以为空(长度为0),但不能是nil
string对象不可以修改
string与[]byte的相互转换需要进行内存拷贝
[]byte临时转换为string不需要内存拷贝
控制结构
defer
延迟函数的参数在defer语句出现时就已经确定
延迟函数的执行顺序为后进先出,压栈的方式
延迟函数可能操作主函数的具名返回值
函数返回过程
将return表达式的值赋给具名返回值(若没有,则赋给临时返回值)
执行defer
ret
select
锁定scase语句中所有的channel
按照随机顺序检测scase中的channel是否ready
若ready,则解锁所有channel
若都不ready,且无default,则将当前goroutine加入所有channel的等待队列,转入阻塞,等待被唤醒
若被唤醒,则返回对应case index
range
range for slice
循环次数在循环开始时就已经确定了
range for map
mapiterinit()函数随机选取一个起始位置进行遍历
why:map的扩容会导致key位置的改变,为了不误导新手,直接从随机的bucket开始遍历
range for channel
channel关闭退出循环,否则阻塞
mutex
state内存布局
Locked表示Mutex是否已被锁定(1位)
Woken表示是否有goroutine已被唤醒(1位)
Starving表示Mutex是否有处于饥饿状态(1位)
Waiter表示阻塞等待锁的goroutine的个数(29位)
两种模式
normal:满足自旋条件,进行自旋抢锁
自旋条件
最大四次自旋、cpu核心数大于1
当前P可运行队列为空
gomaxprocs > 空闲P数量+自旋M数量+1
starvation:我们知道释放锁如果有阻塞等待的协程,则会释放信号量来唤醒一个等待的协程,如果此时锁被自旋协程抢占,被唤醒协程再次阻塞,若阻塞时间超过1ms则进入饥饿模式,不再启动自旋,此时再次被唤醒将直接获取到锁
atomic如何保证原子
总线锁定,不允许其他cpu操作改内存
缓存锁定
缓存一致性协议
某个cpu对缓存数据进行更改时,通知其他cpu放弃缓存
rwmutex
写锁定阻塞读锁定,等待之前所有读锁定完成
写解锁,唤醒所有阻塞读锁定
读解锁(最后一个),唤醒写锁定
读锁定,所有写锁定则阻塞等待
并发控制
waitgroup
信号量
unix系统系统的一种保护共享变量的机制,用于防止多个线程同时访问某个资源
Add操作必须早于Wait,否则会panic
context
Deadline()
返回一个deadline和是否设置deadline标识
Done()
返回一个channel,当context关闭后,Done()返回一个被关闭的通道
Err()
返回context被关闭的原因
Value()
根据key值查询map中的value
反射
反射可以将interface类型转换成反射对象
反射可以将反射对象还原成interface对象
反射对象可以修改,value值必须是可以设置的
协程调度
内存管理
内存对齐
why:从处理器的角度,cpu以字长(32位,64位)为单位访问数据,需要尽可能的减少对内存访问的次数,以实现对数据结构的高效操作
规则:结构体的第一个成员偏移量为0,以后的每个成员相对于结构体首地址的偏移量为 min(该成员大小,编译器默认对齐长度)的整数倍
注意:结构体本身也需要对齐,对齐值为min(最长成员size,编译器默认对齐长度)
空值
struct{},[0]byte不占任何存储空间
空值放在结构体最后一个成员时,需要内存对齐
深挖
指针代表内存单元的编号,一个内存单元是一个字节8bit
64位(8Byte)对齐:数据地址(单位为Byte)需为8的倍数
逃逸分析
栈上分配内存比堆上分配内存效率更高
栈上分配的内存不需要GC,堆上分配的内存需要GC
逃逸分析的目的是决定内存地址分配在栈上还是堆上
逃逸分析是在编译阶段完成
指令
go:linkname
引导编译器将当前私有方法或变量,在编译时链接到指定位置的方法或变量
go:noescape
指定一个有声明没主题的函数,不允许编译器做逃逸分析
go:nosplit
指定该函数跳过堆栈溢出检查
go:noinline
禁止内联
go:norace
禁止竟态检测
go:generate
自动生成代码
cpu
中断
what:cpu运行过程中,当发生其他事件,cpu停止当前程序流,转而处理该事件
软中断
cpu指令,由当前正在运行程序产生
系统调用
程序从用户态转为内核态
执行系统函数
程序从内核态返回用户态
硬中断
外设,磁盘网卡,时钟
时钟周期
时钟频率的倒数,是计算机中最小的时间单位
cpu架构
控制器,运算器,寄存器,L1(数据缓存,指令缓存),L2
寄存器
what:寄存器是cpu内部用于存放数据的小型存储区域,用来暂时存放参与运算的数据及结果和一些cpu运行时需要的信息
通用寄存器、标志寄存器、指令寄存器、控制寄存器...
深挖
指令集
L3,总线
主存,磁盘
缓存
局部性原理
CPU在访问存储设备的时候,都趋于聚集在一片连续的区域中
时间局部性
如果一个信息项正在被访问,那么近期它很可能被再次访问
空间局部性
如果一个存储器的位置被引用,那么它附近的位置也可能将来被引用
缓存一致性协议
MESI
M(修改)
表示缓存块最近被修改过,与内存中的数据不一致,同时在所有私有缓存中只有一份数据
E(独享)
与M状态类似,区别在于该缓存快没有被修改过,与内存中数据一致,所以该CPU要修改缓存块时不需要通知其他CPU
S(共享)
表示至少在其他CPU中存在一份副本,所以当CPU修改该状态的缓存块时,需要通知其他CPU
I(无效)
表示该缓存无效,当读取时,发送缓存未命中
协议消息
Read
该消息包含了所有读取的缓存块的物理地址,表示CPU读取某个缓存块
Read Message
该消息表示Read消息的响应
Invalidate
该消息包含了要失效的缓存块的物理地址
Invalidate acknowledge
表示Invalidate消息的响应
Read Invalidate
该消息是Read和Invalidate的组合,其他CPU必须让其私有缓存块中对应缓存失效
Writeback
该消息表示缓存内的数据写回主存
缓存行cache line
what:cpu缓存中可分配的最小单元,与结构相关,常用64字节
伪共享
what:共享数据被修改,需从内存中重新加载,极大降低cpu效率
how:缓存行填充(缓存行对齐)
缓存淘汰机制
FIFO、LRU
Set Associativity
将缓存划分为若干个cacheline,将指定数量的cacheline关联成一个组,
store buffers
当CPU要修改一个当前缓存块中不存在的变量,那么需要发出Read Invalidate消息并等待其他CPU响应,为了解决这个问题,引入了store buffers
为了不让cpu等待,指令A1将修改的值存入store buffers 并给其他cpu发Read Invalidate消息,cpu继续执行其他指令,此时cacheline为修改前的值
若在其他cpu响应之前,当前cpu指令A2使用了cacheline中修改前的值,则会产生类似指令重排的现象,即A2在A1之前执行
内存屏障
由于引入store buffes 会导致指令重排序问题,而这种重排序会导致可见性问题,所以引入内存屏障来解决这个问题(解决数据只更新到store buffers 而没有更新到cache line 导致的可见性问题)
将store buffers中所有已有变量做标记,后续的wirte操作,必须等待store buffers中所有被标记数据被清空后才更新到cache line
Invalidate Queue
设计思想
写时复制(COW)
如果有多个调用者同时请求相同的资源,它们会获得相同的指针指向相同的资源,直到某个调用者试图修改该资源时,系统才会真正复制一份副本给调用者
主要用于读多写少的场景,如配置文件热更新
CAS
通过不断占用CPU资源来换取加锁的开销
指数退避算法
控制反转(Inversion Of Control)
实现方法:依赖注入(Dependency Injection)
通过控制反转,在对象被创建的时候,通过一个外部容器将依赖对象的应用传递给它,依赖接口而不是具体实现
是面向对象的一种设计思想,用于降低代码之间的耦合
待解决
如何唤醒gorountine
json Marshal 与 UnMarshal
为什么64位原子操作需要64位对齐?
cpu如何执行汇编指令
cpu如何通知其他核放弃缓存?
cpu通知其他核时需要等待吗?
MESI完整过程时怎样?
0 条评论
下一页