前言

最近感觉自己又陷入了无尽的自我循环和自我否定,不知道自己到底是怎么了

我,出生于忧患之中,但是告诫自己,不能死于安乐。

这篇文章是收集的一些Golang进阶的知识,作为我自己更上一层楼的笔记,也希望可以帮助大家

未来将会持续性输出面试之类的八股文和算法之类的笔记,因为我感觉自己需要时间去沉淀

怀才不遇只是欺骗自己的安慰剂

胸无点墨想学太白一字千金?

泛舟池上不如随风远去

人生不止生命不息

Golang进阶需要知道的知识

理解Golang垃圾回收

垃圾回收,一般简称GC,你理解为,释放不需要的资源就行

GC的核心机制,就是后台维护一个守护线程,监控对象状态,识别不需要的对象,释放资源

Golang的垃圾回收机制进行了多次演变

1. v1.0 — 完全串行的标记和清除过程,需要暂停整个程序

2. v1.1 — 在多核主机并行执行垃圾收集的标记和清除阶段

3. v1.3 — 运行时基于只有指针类型的值包含指针的假设增加了对栈内存的精确扫描支持,实现了真正精确的垃圾收集

4. v1.5 — 实现了基于三色标记清扫的并发垃圾收集器,大幅度降低垃圾收集的延迟从几百 ms 降低至 10ms 以下

5. v1.6 — 实现了去中心化的垃圾收集协调器;基于显式的状态机使得任意 Goroutine 都能触发垃圾收集的状态迁移

6. v1.7 — 通过并行栈收缩将垃圾收集的时间缩短至 2ms 以内

7. v1.8 — 使用混合写屏障将垃圾收集的时间缩短至 0.5ms 以内

8. v1.9 — 彻底移除暂停程序的重新扫描栈的过程

9. v1.10 — 更新了垃圾收集调频器(Pacer)的实现,分离软硬堆大小的目标

10. v1.12 — 使用新的标记终止算法简化垃圾收集器的几个阶段

11. v1.13 — 通过新的 Scavenger 解决瞬时内存占用过高的应用程序向操作系统归还内存的问题

12. v1.14 — 使用全新的页分配器优化内存分配的速度

2021年,Golang的垃圾回收机制是三色标记法搭配辅助GC还有写屏障

三色标记法是标记-清除法的一个增强版本

那么,什么是标记清除法呢

简单的说下

标记-清除法的基础原理

就是,先停止服务运行,被引用的对象打上标记

没打上标记的对象就直接清除,也就是回收资源,然后恢复程序运行。

说白了,就是先给你停止任务,正在引用的对象被打个标签,告诉GC,这个是我的人,我罩着的,你不能动,其他的我都用过了,随便你干掉吧,GC才会去清除未引用的标记。

那么三色标记法的原理呢?

首先,三色,是哪三色,是白色灰色黑色

这三个分别代表三种不同的标识状态

白色代表可回收,灰色代表被黑色引用,黑色代表被程序引用

感觉有点绕,这边简单的出个示意图吧

main(主程)
| |
---------------------
A B C D E F G
---------------------
  |-----|
  包含引用

现在我们有个栈,主main引用了A,B两个元素,但是B同时引用了E这个元素,根据三色标记法,三色标记的情况如下

---
A B  标记的黑色对象
---
-
E    标记的灰色对象
-
-------
C D F G  标记的白色对象
-------

所以三色标记法GC的步骤是

1. 扫描全局数据和当前的栈区域,标记引用的对象(A,B)黑色对象
2. 扫描引用的对象,(E) 标记灰色对象
3. 其他的全打入白色对象(C D F G)
4. 重复步骤2,直到灰色对象为空,清空所有白色对象

写屏障,简单点说,就是GC开始的时候,有一个记录器,名字叫屏障,第一次运行的时候,它会扫描各个对象的状态,第二次扫描时,会拿出来和第一次扫描的结果进行比对,也就是三色法那个记录灰色的步骤,标记被引用对象为灰色,防止丢失。

辅助GC,如果GC回收的速度过慢,赶不上程序分配对象的速度,那么这边就会暂时停止分配对象,然后将用户线程抢过来执行GC,其实整个程序现在就是停止的状态,这就是辅助GC

讲了原理,那么我们什么时候去触发GC呢?

  1. 达到内存阈值,阈值是由一个gcpercent的变量控制的,当新分配的内存占已在使用中的内存的比例超过gcprecent时就会触发
  2. 达到定时时间,如果上面的内存阈值一直达不到,那就默认2min触发一次GC。
  3. 手动触发GC,runtime.GC()等

理解Golang的内存分配

首先,什么是内存分配

内存分配的地位?

内存分配和垃圾回收是Golang内存管理的核心双子

内存分配主要解决什么问题?

主要解决协程,对象的内存分配问题

Golang内存分配的算法是?主要的思想是什么?

TCMalloc算法,全程:Thread-Caching Malloc,中文翻译:线程缓存分配

核心的思想就是内存分为多级管理,也就是根据多级缓存,将对象大小分类,根据类别实行不同的分配策略。

每个线程维护独立的内存池,进行内存分配时,首先从独立的内存池申请内存,不足时,才向全局申请内存,避免恶意竞争

对象大小有哪几种分类方法?

  1. 微对象 (0, 168b)
  2. 小对象 (16b, 32KB)
  3. 大对象 (32KB,无限大)

多级缓存是什么

看下这张图


首先看下这个图,这个图里面有三个组件:

1. 线程缓存(Thread Cache)
2. 中心缓存(Central Cache)
3. 页堆(Page Heap),

这三个组件的作用分别是

线程缓存: 线程缓存和线程上的处理器一一绑定,主要用来缓存用户程序申请的微小对象。

中心缓存:Golang内存分配的中心缓存,访问需要互斥锁,主要是用来管理跨度内存管理单元。

页堆:内存分配的核心结构体,Golang会将其作为全局变量存储,是一个全局的缓存列表。

下面来理解多级:

多级,理解为多层级,每个线程都有一个独立的池,所以不需要进行竞争

也就不需要互斥锁进行保护,能够较少抢锁带来的损耗

当自带的缓存不足时,会直接调用中心缓存解决对象的内存分配

如果是大对象,将会被直接进行分配(页堆分配)

分配小对象的步骤

1. 确定分配对象的大小
2. 从线程缓存获取空闲的内存空间
3. 假设线程缓存空闲不足,从中心缓存获取
4. 清除空闲内存(调用runtime.memclrNoHeapPointers)

分配大对象的步骤

1. 获取对象大小,检测对象大小
2. 大于32KB,直接调用(runtime.mcache.allocLarge)分配大内存

理解Golang的runtime

runtime是什么东西?

理解为Golang的基础设施

是一个Golang的内置库

主要就是调度协程,内存分配,GC等一系列基础操作

也是管理goroutine的调度程序

可以理解Golang运行时候系统交互的操作。

详细一点,runtime主要有什么功能

1. GC() 垃圾回收
2. GOMAXPROCS(n) 控制最大CPU
3. Goexit() 终止调用并且退出
4. Gosched() 让出CPU,让其他协程运行,接力协程
5. NumGoroutine:返回正在执行和排队的任务总数
6. NumCPU:返回当前系统的 CPU 核数量

理解goroutine泄漏

goroutine泄漏是什么?

结束goroutine有如下几种方法

1. goroutine完成任务退出
2. 遇到错误
3. 通过信号的方式停止

如果不是通过这三种方式结束的goroutine

那就导致goroutine不会正常退出

然后不断的增长,不会释放,占用过多资源

这就叫goroutine的内存泄漏

举几个goroutine内存泄漏的例子

一般goroutine调度不当,才会出现内存泄漏

一般有如下几个原因可能造成goroutine泄漏

1. 死循环
2. channel机制-持续发送,但不接收
3. channel机制-持续接收,但不发送(空channel)
4. channel机制-缓冲区已满,持续发送

防治goroutine内存泄漏的几个方法

1. 信号控制goroutine,当创建goroutine的时候就要想着结束goroutine

2. 使用channel的时候,最好不要用无限缓存,规定一个缓存数量

3. 避免死循环操作

如何检查goroutine内存泄漏

go pprof, 这个可以看一下我原来的blog

链接在这里

一次Golang服务占用CPU过大的排查经过

Golang的一些常用标准库

os

操作系统功能的相关接口

例如Open, Create, Mkdir, Remove

time

时间相关处理

例如 time.Sleep(time.Second * 1)

fmt

格式化操作

例如 fmt.Println("12")

strconv

提供字符串与基本数据类型互转的能力

string

处理字符串的一些函数集合,包括合并、查找、分割、比较、后缀检查、索引、大小写处理等等。

http

提供web服务

context

上下文操作,我blog里有

sync

提供了基本的同步原语。在多个goroutine访问共享资源的时候,需要使用sync中提供的锁机制。

Golang的package包管理

Go modules管理

1. go.mod 文件,它与 package.json 或 Pipfile 文件的功能类似。
2. 机器生成的传递依赖项描述文件 : go.sum。
3. 不再有 GOPATH 限制。模块可以位于任何路径中。

结尾

大概重要的地方都在这里了,我的基础还是稍稍偏差,不过我倒是觉得,学习嘛,不要停下来,每天进步一点点应该也就好了。

这周六我也要搬到新家去了

音乐工作室也搭建好了,买了电钢

编曲那套东西也都准备完毕了,希望我能做的更好吧

我希望我能越做越好。

将脚步停滞,生命静止

从不明白自己处于什么位置

人生偶尔痛苦还是始终如此?

活着才是唯一值得骄傲的事