笔记链接地址
go的协程轻量级体现在哪
(1) goroutine 是轻量级的用户态线程,上下文切换代价小
- go 将 goroutine 的调度维持在用户态
- 常规线程切换会导致用户态程序代码和内核态操作系统调度程序的切换
- 只涉及PC(程序计数器,标记当前执行的代码的位置) SP(当前执行的函数堆栈栈顶指针) DX三个寄存器的值的修改; 而对比线程的上下文切换则需要陷入内核模式、以及16个寄存器的刷新
(2) 内存占用小: 线程栈空间通常是2M, Goroutine 栈空间最小是2k, golang 可以轻松支持1w 的goroutine运行,而线程数量到达1k(此时基本就达到单机瓶颈了), 内存占用就到2G。
new 和 make 区别
参考博客
- 作用变量类型不同:new 给 string, int, array 分配内存,make 给 slice, map, channel 分配内存;
- 返回类型不一样:new返回指向变量的指针,make返回变量本身;
- 处理方式不同:new 分配的空间被置零。make 分配空间后,会进行初始化;
数组和切片的区别
相同点
- 只能存储一组相同类型的数据结构
- 都是通过下标来访问,并且有容量长度,长度通过 len 获取,容量通过 cap 获取
区别
- 数组是定长,切片长度和容量可以自动扩容
- 数组是是值类型,切片是引用类型(切片底层指向一个数组)
Golang指针传递的优点
- 通过引用类型来传递大的数据结构,可以避免数据结构被复制多次,减少内存的消耗和运行时间的开销。
- 指针传递还可以用于在函数内部修改参数的值,减少函数之间参数传递的时间和开销
Go 有没有引用传递
参考链接
值传递:指在调用函数时将实际参数复制一份传递到函数中
引用传递:指在调用函数时将实际参数的地址直接传递到函数中
有个简单的判断方法:看传进去的参数地址变没变,变了就是值传递,没变就是引用传递
- Go里面没有引用传递,都是值传递。
- map/channel本身就是指针,是引用类型,所以直接传map和channel本身就可以
- 在 Go 语言中,引用类型有 切片(slice)、字典(map)、接口(interface)、函数(func) 以及 通道(chan)
Golang 调度器原理及 GMP 设计思想
基本知识点
- G:go 协程
- M:操作系统的工作线程
- P:go 协程的调度器
- 全局G队列:存放的也是等待运行的G,当P的本地队列为空时,优先从全局队列获取
- P的本地队列:存放的也是等待运行的G,不超过256个,如果队列满了,则会把本地队列中一半的G移动到全局队列
调度器的设计策略
- 复用线程:避免频繁的创建、销毁线程
- 工作窃取(work stealing 机制):当本线程无可运行的G时,尝试从其他线程绑定的P偷取G,而不是销毁线程
- 系统调用 hand off 机制:G进行系统调用阻塞时,线程释放绑定的 P,把 P 转移给其他空闲的线程执行
- 设置P的数量,提高并行能力
- 抢占式调度 (一个goroutine最多占用 CPU 10 ms)
- 1.14 前有局限性,在函数调用中时检查的是否需要抢占
- 调度器每调度 61 次的时候,都会尝试从全局队列里取出待运行的 goroutine 来运行
GC-标记清除算法
参考链接
- 标记清除算法
- 三色并发标记算法(白色 垃圾、灰色 检查态、黑色 有用)
- 插入屏障(仅对堆上的数据有效)
- 插入数据时把数据置为灰色
- 需要 STW 扫描栈上数据
- 插入写屏障不对栈生效,需要执行下 STW 的三色标记法(需要 STW 花费时间)
- 删除屏障
- 删除了会先变成灰色,下一个周期表成垃圾(回收精度低)
- 插入屏障(仅对堆上的数据有效)
- 内存回收的触发时间
- 内存分配量达到阀值触发 gc(每当内存扩大一倍时触发 gc)
- 定期触发 gc(最长 2 分钟触发 gc)
- 执行函数触发 gc
- Gc 性能优化
- 对象数量越多 GC 压力越大(对象复用,或者大对象组合小对象)
- 内存逃逸也会增大 GC 压力
- 参数传递指针也会增加 GC 的压力,不要盲目传递指针
Root set根节点就是发现堆内存可达数据的一组起点,一般为bss段、数据段以及协程栈对应的元数据
Golang 内存分配
一篇文章把 Go 中的内存分配扒得干干净净
- mspan:内存管理的基本单位,将页拆分成块来管理
- mcache:线程的私有资源为单个线程服务
- mcentral:管理特定规格的 mspan,供线程申请使用
- mheap:全局管理申请下来的内存
Golang内存泄漏的7种场景
参考链接
golang pprof实用使用指南(使用 pprof 做性能调试)
- 传参数组过大,导致内存占用过大
- 切片截取引起子切片内存泄漏(解决:make 一个新的切片,把数据 copy 过来)
- Goroutine 阻塞无法退出,导致 goroutine 泄漏
- 协程阻塞(互斥锁未释放、channel 引起的阻塞)
- time.Ticker,每隔指定的执行任务,使用完后必须要释放,否则会造成资源浪费
Go 语言内存逃逸分析
go语言编译器会自动决定把一个变量放在栈还是放在堆,编译器会做逃逸分析(escape analysis),当发现变量的作用域没有跑出函数范围,就可以在栈上,反之则必须分配在堆。
go语言声称这样可以释放程序员关于内存的使用限制,更多的让程序员关注于程序功能逻辑本身 参考链接
go的设计者明明就不希望开发者管这些,但是面试官就偏偏找这种问题问? 醉了也是
参考链接
什么是内存逃逸:一个对象本应该分配在栈上面,结果分配在了堆上面(判断作用域和生命周期在哪里)
内存逃逸的场景:
- 局部指针返回
- 栈空间不足
- 动态类型 interface
- 闭包引用
- 向 channel 发送指针数据
- 在 slice 或 map 中存储指针
影响:大量的对象从栈逃逸到堆上,增加了GC的压力,在GC的过程中会占用比较大的系统开销(一般可达到CPU容量的25%)