Golang笔记
Golang相关配置
golang 配置goproxy可选的地址
IDEA/Goland使用WSL作为默认Terminal
GoLand 2022.1-X专业版激活
Win下用WSL作为Goland终端交叉编译
MacOS下在Goland的Terminal中使用‘ll’命令无效
GoLand 2024.1.X专业版激活
Golang LeeCode练习题
一 Golang数组问题
28. [简单] 寻找数组的中心下标
27. [简单] 数组的度
26. [简单] 最长连续递增序列
25. [简单] 非递减数列
24. [简单] 图片平滑器
23. [简单] 子数组最大平均数 I
22. [简单] 重塑矩阵
21. [简单] 数组拆分 I
20. [简单] 最大连续1的个数
19. [简单] 找到所有数组中消失的数字
18. [简单] 移动零
17. [简单] 丢失的数字
16. [简单] 汇总区间
15. [简单] 存在重复元素 II
14. [简单] 存在重复元素
13. [简单] 多数元素
12. [简单] 两数之和 II
11. [简单] 买卖股票的最佳时机 II
10. [简单] 买卖股票的最佳时机
09. [简单] 杨辉三角 II
08. [简单] 杨辉三角
07. [简单] 合并两个有序数组
06. [简单] 加一
05. [简单] 最大子序和
04. [简单] 搜索插入位置
03. [简单] 移除元素
02. [简单] 删除有序数组中的重复项
01. [简单] 两数之和
29. [简单] 至少是其他数字两倍的最大数
30. [简单] 托普利茨矩阵
31. [简单] 较大分组的位置
32. [简单] 转置矩阵
33. [简单] 公平的糖果棒交换
34. [简单] 单调数列
35. [简单] 按奇偶排序数组
36. [简单] 卡牌分组
37. [中等] 盛最多水的容器
38. [中等] 三数之和
39. [中等] 最接近的三数之和
40. [中等] 四数之和
41. [中等] 下一个排列
42. [中等] 搜索旋转排序数组
43. [中等] 在排序数组中查找元素的第一个和最后一个位置
44. [中等] 组合总和
45. [中等] 旋转图像
Golang完整学习记录
第一章 Go语言简介
20220519@基础环境
20220518@概述
第二章 Go语言基本语法
20220520@基础语法
20220521@正弦函数
20220523@数据类型转换
20220523@指针概念
20220524@堆栈和逃逸分析
20220526@(模拟)枚举
20220528@类型别名
20220528@注释的使用
20220528@关键字与标识符
20220528@运算符的优先级
20220528@数据类型的转换
第三章 Go语言容器
20220531@容器概念
20220531@数组详解
20220531@多维数组
20220605@切片详解
20220606@append的常见操作
20220606@切片元素修改
20220609@多维切片简述
20220609@map映射
20220612@并发(sync)Map
20220614@list(列表)
20220614@nil值/空值/零值
20220615@new和make
第四章 Go语言控制流程
20220615@if分支结构
20220615@for循环
20220615@range遍历
20220615@switch
20220616@goto标签
20220616@break和continue
20220616@聊天机器人
20220620@词频统计
20220622@缩进排序
20220622@二分查找算法
20220622@冒泡排序
20220623@分布式id生成器
第五章 Go语言函数
20220623@函数声明
20220623@函数参数传递效果
20220627@字符串的链式处理
20220630@匿名函数
20220704@函数类型接口
20220704@闭包(Closure)
20220706@可变参数
20220706@defer延迟语句
20220709@递归函数
20220713@处理运行错误
20220714@宕机(panic)
20220714@宕机恢复(recover)
20220715@计算函数耗时
20220718@内存缓存提升性能
20220718@哈希函数
20220720@Test功能测试
第六章 Go语言结构体
20220726@结构体定义
20220726@为结构体分配内存
20220730@实例化结构体
20220803@初始化结构体成员变量
20220810@构造函数
20220816@方法和接收器
20220816@为基本类型添加方法
20220816@使用事件系统实现事件响应和处理
20220817@类型内嵌和结构体内嵌
20220817@结构体内嵌模拟类的继承
20220817@初始化内嵌结构体
20220818@内嵌结构体成员名字冲突
20220823@使用匿名结构体解析JSON数据
20220827@垃圾回收和SetFinalizer
20220828@结构体数据保存为JSON格式
20220901@链表操作
20220908@数据I/O对象及操作
第七章 Go语言接口
20220911@接口定义
20220915@实现接口的条件
20220918@类型与接口的关系
20220918@接口的nil判断
20020918@类型断言简述
20220929@多输出实现日志系统
20221009@排序(by sort.Interface)
20221106@接口的嵌套组合
20221107@接口和类型之间的转换
20221109@空接口类型(interface{})
20221107@空接口实现任意值的字典保存
20221112@switch类型分支
20221201@Error接口返回错误信息
20221229@表达式求值器
20221229@实现Web服务器
20221229@部署Go程序到Linux
20221229@音乐播放器
20221230@有限状态机(FSM)
20221230@二叉树数据结构的应用
第八章 Go语言包概念
20230206@包的基本概念
20230212@封装简介及实现细节
20220212@GOPATH详解
20230212@常用内置包简介
20230212@自定义包
20230212@package(创建包)
20230212@import导入包
20230213@工厂模式自动注册
20230213@单例模式
20230214@sync包与锁
20230215@big包实现整数的高精度计算
20230215@使用图像包制作GIF动画
20230216@正则regexp包
20230218@time包:时间和日期
20230219@go mod包依赖管理工具
20230219@os包用法简述
20230219@flag包:命令行参数解析
20230219@生成二维码
20230219@Context(上下文)
20230220@示例:客户信息管理系统
20230221@发送电子邮件
20230222@Pingo插件化开发
20230221@定时器实现原理及作用
第九章 Go语言并发
20230224@并发简述(并发的优势)
20230224@goroutine(轻量级线程)
202300226@并发通信channe简介
20230226@竞争状态简述
20230227@GOMAXPROCS(并发运行性能)
20230227@并发和并行的区别
20230227@goroutine和coroutine的区别
20230227@通道(channel)—goroutine之间通信的管道
20230227@并发打印(借助通道实现)
20230227@单向通道——通道中的单行道
20230301@无缓冲的通道
20230301@带缓冲的通道
20230302@channel超时机制
20230302@通道的多路复用
20230302@RPC(模拟远程过程调用)
20230304@使用通道响应计时器的事件
20230306@关闭通道后继续使用通道
20230306@多核并行化
20230306@Telnet回音服务器-TCP服务器的基本结构
20230307@竞态检测——检测代码在并发环境下可能出现的问题
20230310@互斥锁(sync.Mutex)和读写互斥锁(sync.RWMutex)
20230310@等待组(sync.WaitGroup)
20230310@死锁、活锁和饥饿概述
20230311@封装qsort快速排序函数
20230311@CSP:并发通信顺序进程简述
20230312@聊天服务器
20230313@如何更加高效的使用并发
20230313@使用select切换协程
20230313@加密通信
第十章 Go语言反射
20230317@反射(reflection)简述
20230318@反射规则浅析
20230319@反射的性能和灵活性测试
20230322@通过反射获取类型信息(reflect.TypeOf()和reflect.Type)
20230325@通过反射获取指针指向的元素类型(reflect.Elem())
20230325@通过反射获取结构体的成员类型
20230325@结构体标签(Struct Tag)
20230325@通过反射获取值信息(reflect.ValueOf()和reflect.Value)
20230326@通过反射访问结构体成员的值
20230326@判断反射值的空和有效性(IsNil()和IsValid())
20230327@通过反射修改变量的值
20230327@通过类型信息创建实例
20230327@通过反射调用函数
20230327@依赖注入(inject库)
第十一章 文件处理
20230327@自定义数据文件
20230328@JSON文件的读写操作
20230402@XML文件的读写操作
20230402@使用Gob传输数据
20230404@纯文本文件的读写操作
20230405@二进制文件的读写操作
20230405@自定义二进制文件的读写操作
20230405@zip归档文件的读写操作
20230405@tar归档文件的读写操作
20230408@使用buffer读写文件
20230409@实现Unix中du命令统计文件
20230410@从INI文件中读取配置
20240411@文件的读写追加和复制
202304111@文件锁操作
第十二章 Go语言编译与工具
20230411@go build命令使用
20230413@clean命令-清除编译文件
20230413@run命令-编译并运行
20230413@fmt命令-格式化代码文件
20230413@install命令-编译并安装
20230414@go get命令-获取代码编译并安装
20230414@go generate命令-在编译前自动生成某类代码
20230415@go test命令-单元和性能测试
20230415@go pprof-性能分析命令
20230415@Go语言与C/C++进行交互
20230415@Go语言内存管理简述
20230415@Go语言垃圾回收
20230415@Go语言实现RSA和AES加解密
Golang简单实战
Golang根据书籍ISBN爬取豆瓣评分和评论数
Go编写使用指定的CPU百分比消耗CPU资源
Golang的日常应用
使用 FFmpeg 进行实时码率检测
WSL的远程开发应用
WSL2设置静态IP
在WSL2中启动SSH
使用CentOS7作为Goland终端的修改项
Golang学习路线
Go开发者成长路线图
本文档使用 MrDoc 发布
-
+
home page
20230415@Go语言内存管理简述
内存管理是非常重要的一个话题。关于编程语言是否应该支持垃圾回收就有个搞笑的争论,一派人认为,内存管理太重要了,而手动管理麻烦且容易出错,所以我们应该交给机器去管理。另一派人则认为,内存管理太重要了!所以如果交给机器管理我不能放心。争论归争论,但不管哪一派,大家对内存管理重要性的认同都是勿庸质疑的。 Go语言是一门带垃圾回收的语言,Go语言中有指针,却没有C语言中那么灵活的指针操作。大多数情况下是不需要用户自己去管理内存的,但是理解Go语言是如何做内存管理对于写出优秀的程序是大有帮助的。 # 内存池概述 Go语言的内存分配器采用了跟 tcmalloc 库相同的实现,是一个带内存池的分配器,底层直接调用操作系统的 mmap 等函数。 作为一个内存池,它的基本部分包括以下几部分: 1. 首先,它会向操作系统申请大块内存,自己管理这部分内存。 2. 然后,它是一个池子,当上层释放内存时它不实际归还给操作系统,而是放回池子重复利用。 3. 接着,内存管理中必然会考虑的就是内存碎片问题,如果尽量避免内存碎片,提高内存利用率,像操作系统中的首次适应,最佳适应,最差适应,伙伴算法都是一些相关的背景知识。 >Go语言是一个支持 goroutine 这种多线程的语言,所以它的内存管理系统必须也要考虑在多线程下的稳定性和效率问题。 在多线程方面,很自然的做法是 **<span style="font-family:楷体">每条线程有自己的本地内存,然后有一个全局的分配链,当某个线程中内存不足后就向全局分配链中申请内存。这样就避免了多线程同时访问共享变量时的加锁。</span>** 在避免内存碎片方面: - 大块内存直接按页为单位分配 - 小块内存会切成各种不同的固定大小的块,申请任意字节内存时会向上取整到最接近的块,将整块分配给申请者以避免随意切割。 # 大小内存的分配 Go语言中为每个系统线程分配一个本地的 MCache(前面介绍的结构体 M 中的 MCache 域): - 小于 32K 为小对象:少量的地址分配就直接从 MCache 中分配,并且定期做垃圾回收,将线程的 MCache 中的空闲内存返回给全局控制堆。==小对象从本地内存链表中分配== - 大于 32K 为大对象:大对象直接从全局控制堆上以页(4k)为单位进行分配,也就是说大对象总是以页对齐的。一个页可以存入一些相同大小的小对象,==大对象从中心内存堆中分配== <span style="font-family:楷体">大约有 100 种内存块类别,每一个类别都有自己对象的空闲链表。小于 32kB 的内存分配被向上取整到对应的尺寸类别,从相应的空闲链表中分配。一页内存只可以被分裂成同一种尺寸类别的对象,然后由空闲链表分配器管理。</span> # 分配器的数据结构 分配器的数据结构包括: - FixAlloc:固定大小(128kB)的对象的空闲链分配器,被分配器用于管理存储; - MHeap:分配堆,按页的粒度进行管理(4kB); - MSpan:一些由 MHeap 管理的页; - MCentral:对于给定尺寸类别的共享的 free list; - MCache:用于小对象的每 M 一个的 cache。 我们可以将Go语言的内存管理看成一个两级的内存管理结构 `MHeap` 和 `MCache`。 1. 上面一级管理的基本单位是页,用于分配大对象,每次分配都是若干连续的页,也就是若干个 4KB 的大小。使用的数据结构是 MHeap 和 MSpan,用 BestFit 算法做分配,用位示图做回收。 2. 下面一级管理的基本单位是不同类型的固定大小的对象,更像一个对象池而不是内存池,用引用计数做回收。下面一级使用的数据结构是 MCache。 ## MHeap Go语言的程序在启动之初,会一次性从操作系统那里申请一大块内存作为内存池。这块内存空间会放在一个叫 `Mheap `的 struct 中管理,`Mheap `负责将这一整块内存切割成不同的区域,并将其中一部分的内存切割成合适的大小,分配给用户使用。 `MHeap` 层次用于直接分配较大(>32kB)的内存空间,以及给 `MCentral` 和 `MCache` 等下层提供空间。它管理的基本单位是 `MSpan`。`MSpan `是一个表示若干连续内存页的数据结构,简化后如下: ```go struct MSpan { PageID start; // starting page number uintptr npages; // number of pages in span }; ``` 通过一个`基地址 +(页号*页大小)`,就可以定位到这个 `MSpan` 的实际的地址空间了,基地址是在 `MHeap` 中存储的,`MHeap` 负责将`MSpan`组织和管理起来。 `free` 是一个分配池,从 `free[i]`出去的 `MSpan` 每个大小都是 `i` 页的,总共 256 个槽位。再大了之后,大小就不固定了,由 `large` 链起来。 Go语言在这里使用的类似于位示图,可以看到 MHeap 中有一个。 ```go MSpan *map[1<<MHeapMap_Bits]; ``` 这个数组是一个用于将内存地址映射成 `MSpan` 结构体的表,每个内存页都会对应到 map 中的一个 `MSpan` 指针,通过 map 就能够将地址映射到相应的 `MSpan`。 具体做法是: 1. 给定一个地址,通过 `(地址-基地址) / 页大小`得到页号 2. 再通过 `map[页号]` 就得到了相应的 MSpan 结构体。 >前面说过 MSpan 就是若干连续的页。那么,一个多页的 MSpan 会占用 map 数组中的多项,有多少页就会占用多少项。比如,map[502] 到 map[505] 可能都指向同一个 MSpan,这个 MSpan 的 PageId 为 502,npages 为 4。 回收一个 MSpan 时,首先会查找它相邻的页的址址,再通过 map 映射得到该页对应的 MSpan,如果 MSpan 的 state 是未使用,则可以将两者进行合并。最后会将这页或者合并后的页归还到 free[] 分配池或者是 large 中。 ## MCache MCache 层次跟 MHeap 层次非常像,也是一个分配池,对每个尺寸的类别都有一个空闲对象的单链表。Go 的内存管理可以看成一个两级的层次,上面一级是 MHeap 层次,而 MCache 则是下面一级。 每个 M 都有一个自己的局部内存缓存 MCache,这样分配小对象的时候直接从 MCache 中分配,就不用加锁了,这是Go语言能够在多线程环境中高效地进行内存分配的重要原因。MCache 就是用于小对象的分配。 分配一个小对象(\<32kB)的过程: 1. 将小对象大小向上取整到一个对应的尺寸类别,查找相应的 MCache 的空闲链表。如果链表不空,直接从上面分配一个对象。这个过程可以不必加锁。 2. 如果 MCache 自由链是空的,通过从 MCentral 自由链拿一些对象进行补充。 3. 如果 MCentral 自由链是空的,则通过 MHeap 中拿一些页对 MCentral 进行补充,然后将这些内存截断成规定的大小。 4. 如果 MHeap 是空的,或者没有足够大小的页了,从操作系统分配一组新的页(至少 1MB)。分配一大批的页分摊了从操作系统分配的开销。 注意上面表述中的用词“一些”。从 MCentral 中拿“一些“自由链对象补充 MCache 分摊了访问 MCentral 加锁的开销。从 MHeap 中分配“一些“的页补充 MCentral 分摊了对 MHeap 加锁的开销。 释放一个小对象也是类似的过程: 1. 查找对象所属的尺寸类别,将它添加到 MCache 的自由链。 2. 如果 MCache 自由链太长或者 MCache 内存大多了,则返还一些到 MCentral 自由链。 3. 如果在某个范围的所有的对象都归还到 MCentral 链了,则将它们归还到页堆。 归还到 MHeap 就结束了,目前还是没有归还到操作系统。 MCache 层次仅用于分配小对象,分配和释放大的对象则是直接使用 MHeap 的,跳过 MCache 和 MCentral 自由链。MCache 和 MCentral 中自由链的小对象可能是也可能不是清 0 了的。对象的第 2 个字节作为标记,当它是 0 时,此对象是清 0 了的。页堆中的总是清零的,当一定范围的对象归还到页堆时,需要先清零。这样才符合Go语言规范:分配一个对象不进行初始化,它的默认值是该类型的零值。 ## MCentral MCentral 层次是作为 MCache 和 MHeap 的连接。对上,它从 MHeap 中申请 MSpan;对下,它将 MSpan 划分成各种小尺寸对象,提供给 MCache 使用。 ```go struct MCentral { Lock; int32 sizeclass; MSpan nonempty; MSpan empty; int32 nfree; }; ``` >注意,每个 MSpan 只会分割成同种大小的对象。每个 MCentral 也是只含同种大小的对象。MCentral 结构中,有一个 nonempty 的 MSpan 链和一个 empty 的 MSpan 链,分别表示还有空间的 MSpan 和装满了对象的 MSpan。 分配还是很简单,直接从`MCentral->nonempty->freelist` 分配。如果发现 freelist 空了,则说明这一块 MSpan 满了,将它移到 MCentral->empty。 前面说过,回收比分配复杂,因为涉及到合并。这里的合并是通过引用计数实现的。从 MSpan 中每划出一个对象,则引用计数加一,每回收一个对象,则引用计数减一。如果减完之后引用计数为零了,则说明这整块的 MSpan 已经没被使用了,可以将它归还给 MHeap。
Nathan
April 15, 2023, 9:18 p.m.
转发文档
Collection documents
Last
Next
手机扫码
Copy link
手机扫一扫转发分享
Copy link
Markdown文件
PDF文件
Docx文件
share
link
type
password
Update password