Go基础
切片
- 通过 make 方法去声明一个 int 32 类型的一个slice,指定它的长度为10,容量为20。那么对于这样生成的一个变量,它在内存当中占据多大的空间?
- 切片对象占用内存大小:int 类型的 cap、len 字段,一个底层数组的指针。8+8+8=24 字节
- 底层数组占用内存大小:int32 类型,长度为 20 的数组。4*20=80 字节
- 总大小:24+80=104 字节
- append 一个 slice 的一个元素之后,我们并不使用一个新的一个切片来去接收这个 append 的返回,那么会有问题吗?
- 一开始有个a,然后 slice 里面有包含123,嗯三个元素,然后我要在这个 a 的 slice 上 append 一个元素4,但是我 append 完了之后,正常 append 我肯定返回了一个新的slice。对,我并不接收它,那么我这时候再把 a 打印出来会不会有问题呢?
- 如果我一开始那个 capability 就是那个 cap 是足够的话,它会怎么样?
- 切片的容量足够的话,会将添加的元素放到底层数组里,但是之前的切片对象访问不到新添加的元素,因为 length 没有增加。
- 如果容量不够,就会创建一个新的数组,拷贝之前切片里的数据,并添加新元素。这不会影响旧切片。
切片对象包含长度、容量、指向底层数组的指针
容量不够时,创建一个大概两倍的数组,并把旧数组的数据拷贝到新数组
- append 的元素数量 + 切片长度大于切片容量的两倍时,新长度等于append 的元素数量 + 切片长度
- 当原数组的长度小于 1024 时,扩大 2 倍
- 当原数组的长度大于等于 1024 时,每次增加 1/4,直到大于等于append 的元素数量 + 原数组长度
- 新容量计算出来后,还要考虑内存对齐
- 切片和数组的区别
- 切片是否并发安全
- go 的切片的原理大概可以解释一下吗?
切片包含容量、长度和指向底层数组的指针。数据存放在底层数组。
- 它和数组的区别是什么?
- 数组长度固定,切片可以扩容
- 在参数传递时,如果数据量比较大,数组参数比切片参数耗费性能
- 切片的扩容方式是什么?
一般是原数组扩大两倍,如果原切片长度大于等于 1024,就会每次扩大 1/4,直到能放下新增的元素。如果我们一次追加的元素过多,以至于使新长度比原容量的2倍还要大,那么新容量就会以新长度为基准。最后再考虑内存对齐。
- 那拷贝大切片是不是比这个拷贝小切片所需的资源更大?
不会,拷贝切片并没有拷贝底层数组,切片只包含容量、长度、指向底层数组的指针这三个字段
- 那你刚刚说扩容是怎么扩的呢?
- 那也就是说扩容的代价非常大,是不是?
是的,尽量减少扩容。例如能预估切片的容量时,可以在创建切片时指定容量。
- 什么是 0 切片或者空切片?
0 切片指的是为 nil 的切片,由于零值可用的特性,可以直接 append。
空切片是指长度为 0 的切片,容量可以不为 0。
- 内存对齐这个概念有了解吗?
为了减少 cpu 和内存的交付。为了平衡 cpu 和内存的速度差异,产生了 cpu 缓存,来减少 cpu 和内存的交付。同时根据局部性原理,cpu 每次读取数据时,会读取一块数据,也就是 cache line。在 64 位的机器上,一般是 8 字节。 举个例子,golang通过内存对齐,将 int64 存放到 1-8 的虚拟内存地址上,这样 cpu 只需要读取一次即可。如果没有内存对齐,将数据放到了 5-12,那么就需要读取两次,第一次读取 1-8,第二次读取 9-16。
- 一个空的切片跟空的这个map,你去对它去追加元素的时候,它的预期行为是什么?
切片的话 0 值是可用的,你可以直接append。但是如果是个 map 的话,没有 make 初始化,去读写会panic。
Map
- 我有一个 for range 循环。我去遍历了一个 map a,然后我可以在这个 for range 当中拿到这个 map 的 key 和value,是吧?然后我在这个循环当中,我通过 print 方法打印出这个 map 的这个 key 和这个value,然后我紧接着调用了 DELETE 方法删除了这个 map a 当中对应的这个key,那么对于这样的一个循环而言,它可以正确的输出这个 map 当中的每一个对应的元素和它对应的值吗?
在遍历 map 的过程中删除元素是安全的,删除操作不会影响迭代器的状态。
并发读是安全的,并发读写、并发写是不安全的。map在读写时会判断标志位,存在上述情况时就 throw 异常,无法 recover。
- 分段锁,降低锁的颗粒度。
- 自己加锁。
- 使用sync.map替代。写数据时实现了加锁,使用了空间换时间的方式,用两个哈希结构存储Map,有一层缓存,加速读取数据。
channel
- 对已经关闭的channel进行读写操作会发生什么?
- 如果在关闭前,通道内部有元素,会正确读到元素的值;如果关闭前通道无元素,则会读取到通道内元素类型对应的零值。
- 在实际开发中,读取 channel 需要先判断是否关闭。
- 写已关闭的通道会引发panic: send on closed channel
- 关闭已关闭的通道会引发panic: close of closed channel
并发
- golang 子协程 panic,父协程能否 recover 捕获到?
不能,panic
只能触发当前 Goroutine
的 defer
调用,在 defer
调用中如果存在 recover
,那么就能够处理其所抛出的 panic 事件。
- 有 x 的任务,你需要尽快的将这 x 的任务并发的执行完成。但是我限制你使用 goroutine 的数量为y, x 是大于等于 y 的。那么你怎么样去设计这样一个简单的并发模型?
把 x 任务里面放进channel。然后会用 for 循环去起对应数量的协程,然后从这个 channel 里面去消费任务
- MPG 模型
- 全局队列是怎么产生的?
新建 G时,G优先加入到 P 的本地队列,如果队列满了,则会把本地队列中一半的 G 移动到全局队列。
P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其他 P 的本地队列偷一半放到自己 P 的本地队列。
- 比如说你写了一个 go 的一个多协程的一个服务,那如果,比如说有一个其中一条协程,它调了一个阻塞式的一个调用,比如说, net count read,或者是一个磁盘的 read 或者write,有可能因为网络不好,或者磁盘太差了,有可能会阻塞住嘛。嗯,我的问题是,如果 IO 或者网络阻塞住之后,那么这个 grooting 所在的那个线程它是否会阻塞?
会挂起,P 会找其他 M 继续执行队列里的 G。当系统调用返回后,G 会加入到 P 里等待执行
- 如果 Http Server 如果一次性来了太多的请求,那么我们知道那个 Http 默认的那个框架,它是每个请求来会起一个goroutine。那如果这种情况会不会引发大规模的线程阻塞呢?
不会,因为 P 的数量是固定的,创建很多 goroutine 的话,会先进入 P 的本地队列,本地队列满了就会进入全局队列。
- 比如一个携程正在执行任务,我希望实现这么一个特性,这个任务如果 5 秒钟之内没执行完,那么把这个任务取消掉,把个协程给干掉,应该怎么做?
context.WithTimeout()
- wait group 的使用
- 如何使用并发安全的操作,不使用锁的情况如何实现
chanel,原子操作
- 并发原语
Mutex、RWMutex、WaitGroup、Once、Cond,以及抽象层级更高的Channel
性能优化
- Golang 方面的话,你有相关的运行时的调优经验吗啊?比方说我有一个嗯运行时的程序,嗯,那么它可能发生了一些异常的内存泄漏、 CPU 泄露的场景,你知道怎么样去对应的进行排查吗?
使用 PProf 做内存分析
- 比如说线上,goroutine泄露或者内存泄露,像这种一般来说你怎么排查?
- 浏览器查看 goroutine 信息:http://127.0.0.1:6060/debug/pprof/goroutine?debug=1
- 命令行:go tool pprof http://127.0.0.1:6060/debug/pprof/goroutine,traces 查看所有 goroutine 调用栈信息
- 内存泄露:go tool pprof http://127.0.0.1:6060/debug/pprof/heap,top
- 查看锁相关信息:go tool pprof -http=":8080" http://localhost:6060/debug/pprof/mutex
- 隔几秒采样 goroutine 信息,如果发现有协程总是在等待锁,重点排查下。
- 内存泄漏,cpu 飙升如何排查,goroutine 泄露排查
使用PProf生成性能数据文件(heap, profile, groutine),进行分析,调用关系图,火焰图,go tool 交互式top命令。
- goroutine 泄露的场景
未管理子协程的生命周期
- 有过 go 的 gc 的调优经验吗?
- 通过压测定位热点代码,可以考虑使用对象池优化
- 减少内存逃逸
- 函数内创建的对象的指针作为函数返回值
- map、slice、channel 包含的指针元素
- 被已逃逸的对象所引用
- 超过 64k 的内存占用放到堆上,例如nums2 := make([]int, 8192)
- 那像 go 其实它的性能本身是很优秀的,那就说但是它也有它唯一的缺点,就在于说 GC 的时候。那我们平时编码的时候有什么要注意的点?针对是 go 这个 GC 耗性能的,或者就 go 的 GC 可能会把服务给阻塞,就说因为它每次 GC 时候它就说会耗大量的这种计算嘛。在你的编码设计中怎么来去减少 go 的GC?
- 尽可能避免逃逸,因为栈内存效率更高,还不用 GC。
- 比如小对象的传参,array 要比 slice 效果好。
- 函数频繁创建的简单的对象,直接返回对象。
- 那么对于频繁的内存申请操作,使用 sync.Pool对象池。
- 在压测的情况下我怎么去定位我服务的一些热点?
通过 PProf 的火焰图,优步也有一个火焰图库。
其他
可以,会比较字段
map、slice、func 变量只能和 nil 比较,不能互相比较,所以不能用于 map 的 key。
- 主动调用runtime.GC
- 新分配的堆内存是上一GC周期中活跃堆内存的一倍,可通过 GOGC 参数来调整。
- 后台定时触发
Redis
1、string(字符串);2、hash(哈希);3、list(列表);4、set(集合);5、sort set (有序集合)
- 如何向一个哈希 key 当中去批量的添加一些它的子 key?
HMSET user:1000 username "john" email "john@example.com" age 25
- 有序集合如何获取score在 1- 100 之间的元素
ZRANGEBYSCORE myzset 1 100
底层数据结构是压缩列表或跳表,在保存数据时会根据 score 排序。
底层数据结构是压缩列表或跳表
- 缓存穿透
- 布隆过滤器
- Redis 的分布式锁是安全的吗?
Mysql
- MySQL 里面的这个索引,它是一个什么样的一个结构?
- 那这个 b+树它的每一层里面是什么样的数据啊?然后那个包括它的这个查找的过程就是能再讲一下吗?
- 中间的那些层非叶子节点存了哪些数据
- 最左匹配原则是指的什么
网络
物理层,数据链路层,网络层,传输层,会话层,表示层,应用层
ARP,RARP,ICMP,IPSec
ICMP
- TCP 连接怎么断开的?
- 首先发起断开请求的一番,会进入到一个 time wait 状态,为什么会有这个状态?
- 防止历史数据包被下一个同样的 TCP 四元组连接接收
- 保证被关闭方能正确关闭连接
- 在浏览器里面输入 www.baid.com,这个请求到达对端的一个服务器,会经过网络节点,这个过程里面涉及到哪些协议呢?我这个请求怎么样才能到达我的目标机器呢?
- 请求怎么到达对端的服务器?因为他怎么知道我要去的服务器是哪里?等等一系列过程,稍微复杂一点。
- 说一下三次握手的过程,然后说一下为什么需要三次握手,而不是两次握手。
操作系统
信号、消息队列、管道、socket、共享内存(工作中有遇到,高性能传递文件,减少 IO)
select, poll, epoll
- 在IO 多路复用模型中,比方说当我文件系统文件描述服里面就绪的系统文件描述服很多的时候,百万上百万就绪的时候,我每一次遍历循环,其中那个文件描述符从头遍历到尾,它需要很长的一个时间,是吧?而且很浪费性能。那么对于这样的方式,你有什么好的优化经验吗?
epoll采用事件驱动,发生事件后放到队列里,用户进程通过 epoll.wait 获取事件,内核会把事件和对应的文件描述符拷贝到用户空间,用户进程执行相应的处理函数。文件描述符的查找是通过红黑树查询的,复杂度为 O(logn)
- 比如说我们 Linux 服务器登录进去之后,我们觉得它非常的负载非常高,或者比较卡顿,一般怎么去判断它是不是哪里的负载过高呢?
top 命令。注意可能出现 cpu 利用率不高,但是负载高的情况。因为负载统计的是可运行的进程数+休眠不可中断的进程数(例如IO等待)。
- 那比如说 IO 过高的话,那它是怎么体现在负载上的?比如说就负载我们知道有个 load average 的数值,对,那就比如说那个数值跟负载不跟那个 IO 的话,它怎么关联上来?
- 那比如负载那个数值,它那个数值是具体是什么数值呢?
- 它的任务指的是进程还是线程?
进程
- 说一下那个线程和进程区别
- 如何查询一个端口被占用,或者一个文件被哪打开。
- pagecache 淘汰机制
算法题
- n 乘 n 的二维矩阵旋转 90 度输出
- 判断链表有环
- 一个数组,求最长递增子序列,不是子串,不要求连续
- 两个字符串,求最长公共子序列
逻辑题
老师的生日,月份告诉小明,日告诉小强,黑板上十个日期,加上小明小强的对话,推测老师的生日。
Post Views: 9