go语言基础学习六
简述go内存管理机制#
TCMalloc 核心思想#
分层缓存:线程私有缓存 + 全局中心缓存 + 堆,优先本地无锁分配#
线程私有缓存(对应 Go:mcache/P 私有)#
每个线程自用一小块内存池,分配不用加锁,最快,不够再找上层。
全局中心缓存(对应 Go:mcentral)#
所有线程共用的池子,私有缓存空了就批量取一批内存,减少频繁抢锁
系统堆(对应 Go:mheap)#
向 OS 申请大块内存,中心池耗尽才去向 OS 要内存
按规格分桶#
预先划分几十种固定内存规格(8B、16B…32KB),一个桶只存同大小对象
释放的内存放回原规格桶,下次分配直接复用
避免小块、大块内存交错混杂,大幅减少内存碎片
批量存取:本地缺内存时批量从中心拿,减少锁竞争与系统调用。#
单个要:频繁加锁,耗性#
批量拿:一次锁,领一批#
Go 内存管理(TCMalloc 落地版)#
三层缓存架构#
mcache(P私有,无锁) → mcentral(全局同规格共享) → mheap(对接OS)
分桶管理 (mspan)#
内存划固定尺寸规格,单个 span 只存同种大小对象,抑制内存碎片。
批量申领#
mcache 耗尽,一次性从 mcentral 批量拉多个 span,减少频繁加锁;mcentral 缺资源,mheap 批量向 OS 申请大块内存,减少系统调用。
mutex有几种模式?#
mutex有两种模式:normal 和 starvation
正常模式#
所有goroutine按照FIFO的顺序进行锁获取,被唤醒的goroutine和新请求锁的goroutine同时进行锁获取,通常新请求锁的goroutine更容易获取锁(持续占有cpu),被唤醒的goroutine则不容易获取到锁。公平性:否。
饥饿模式#
所有尝试获取锁的goroutine进行等待排队,新请求锁的goroutine不会进行锁获取(禁用自旋),而是加入队列尾部等待获取锁。公平性:是。
go如何进行调度的?GMP中状态流转#
Go里面GMP分别代表:G:goroutine,M:线程(真正在CPU上跑的),P:调度器Processor。
关系#
P 绑定 M,M 运行 P 上的 G
G 五种核心状态#
Grunnable 就绪#
在 P 本地队列 / 全局队列,等着被调度运行
Grunning 运行#
G 挂载 M,M 绑定 P,正在执行
Gwaiting 阻塞#
阻塞 syscall:M 和 G 脱离 P,P 找新 M 继续跑别的 G;阻塞返回后 G 变回就绪丢队列#
han/sleep:G 挂在阻塞链表,P、M 解绑复用#
Gdead 死亡#
执行结束,可复用或销毁
调度流转要点#
P 优先取本地就绪 G运行,无则偷其他 P 队列 G(work-stealing 窃取),全空去全局队列拿 G#
G 遇阻塞:P 释放当前 M,换 M 跑新 G,规避内核线程浪费#
M 空闲:找 P 绑定,没有就休眠#
Go什么时候发生阻塞?阻塞时,调度器会怎么做#
G 三种阻塞场景#
系统调用阻塞(文件 IO、socket 读写)#
Go 运行时阻塞(chan、sleep、sync 锁)#
主动让出 runtime.Gosched ()#
阻塞调度行为#
系统调用阻塞#
M+G 离开 P,P 空闲; P 去找空闲 M / 新建 M,继续跑队列里其他 G; 系统调用返回:G 变就绪,放回 P 队列,M 休眠
chan / 锁 /sleep 阻塞#
G 切为 Waiting,M 不脱离 P,P 立刻从队列拿下个 G 继续执行。
Gosched 主动让出#
G 变回就绪丢队列,P 调度下一个 G。
总结#
syscall:P 放走 M;go 语法阻塞:G 挂起、M 留 P 继续干活
go-six
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法