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 继续干活