GO 八股整理(自用)

文章目录

slice和array的区别

维度 array(数组) slice(切片)
长度特性 长度固定(声明时指定,如[5]int 长度可变(动态扩容)
类型标识 长度是类型的一部分([3]int[5]int是不同类型) 长度不是类型的一部分([]int统一类型)
内存结构 直接存储元素(值类型) 存储指针、长度、容量(引用类型,指向底层数组)
传递方式 传值(复制整个数组) 传引用(复制切片头,共享底层数组)

slice 的 len、cap、共享、扩容

  • len :切片当前元素个数;cap:底层数组的容量。
  • 共享:多个切片可指向同一底层数组,修改其中一个会影响其他切片。
  • 扩容 :当appendlen>cap时,分配新数组并复制数据:
    • 原容量≤1024:新容量=原容量×2;
    • 原容量>1024:新容量=原容量×1.25;
    • 若追加元素过多,直接按"原长度+新增长度"扩容。

map、slice未初始化的操作结果及panic处理

  • 未初始化的slice :默认是nil,可执行读操作(返回零值)、len/cap(返回0),但写操作会panic
  • 未初始化的map :默认是nil,可执行读操作(返回零值)、len(返回0),但写操作会panic

panic的处理方式

  • 通过defer + recover()捕获panic,清理资源后恢复程序运行;
  • 避免panic的核心是:操作前初始化(slice = make([]int, 0)map = make(map[string]int))。

recover的使用及defer的优势

  • recover的使用

    必须在defer函数中调用,用于捕获当前Goroutine的panic,返回panic的错误信息(无panic时返回nil)。

    示例:

    go 复制代码
    defer func() {
        if err := recover(); err != nil {
            fmt.Printf("捕获panic:%v", err)
        }
    }()
  • defer的优势

    相比"函数最后手动执行操作",defer能保证操作无论函数正常返回还是panic,都会执行(如资源释放、锁解锁),避免资源泄漏。

map的有序性、并发安全

  • map是无序的

    Go的map底层是哈希表,遍历是按哈希桶的顺序遍历,与插入顺序无关;即使Go 1.21+对遍历做了随机化优化,仍不保证有序。

  • map不是并发安全的

    并发读写会直接panic。保证并发安全的方式

    1. map + sync.RWMutex(读写锁);
    2. sync.Map(标准库提供的并发安全map,适用于读多写少)。

控制GMP中M的数量

GMP是Go的调度模型(G:Goroutine,M:操作系统线程,P:处理器),可通过环境变量GOMAXPROCS控制P的数量 (默认等于CPU核心数),但无法直接控制M的数量 ------M由Go运行时根据调度需求动态创建/销毁(默认无上限,极端场景可通过debug.SetMaxThreads限制)。

控制Goroutine的生命周期

  • channel的作用

    通过关闭channel或向channel发送信号,让Goroutine感知并退出(如done <- struct{}{})。

  • context的作用

    传递上下文(如超时、取消信号),控制Goroutine的生命周期,避免Goroutine泄漏(常用context.WithCancel/WithTimeout)。

select的使用

select用于同时监听多个channel的操作(读/写),语法类似switch,执行任意一个就绪的case;若所有case都未就绪,则阻塞(或执行default)。

示例:

go 复制代码
select {
case msg := <-ch1:
    fmt.Println("收到ch1消息:", msg)
case ch2 <- 10:
    fmt.Println("向ch2发送消息")
default:
    fmt.Println("无channel就绪")
}

new和make的区别

函数 适用类型 返回值类型 作用
new 任意类型 指针(*T 分配内存,初始化零值
make 仅slice、map、channel 类型本身(T 分配内存并初始化(如slice的底层数组)

context机制介绍

context是Go用于传递请求上下文 的标准库(context.Context),核心作用是在Goroutine之间传递取消信号、超时信号、元数据,控制Goroutine的生命周期,避免资源泄漏。

核心特性:
  • 可派生 :通过context.Background()(根上下文)派生子上下文(如WithCancel/WithTimeout/WithValue);
  • 链式传递 :子Goroutine通过参数接收context,感知上游的取消/超时事件;
  • 不可修改Context接口的方法都是只读的,保证上下文的一致性。
常用方法:
  1. context.WithCancel(parent):派生可手动取消的上下文;
  2. context.WithTimeout(parent, duration):派生超时自动取消的上下文;
  3. context.WithValue(parent, key, val):传递元数据(仅用于请求范围的轻量数据)。
示例(控制Goroutine退出):
go 复制代码
func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done(): // 感知取消/超时
            fmt.Println("worker退出:", ctx.Err())
            return
        default:
            fmt.Println("worker运行中...")
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel() // 确保资源释放
    go worker(ctx)
    time.Sleep(5 * time.Second)
}

GC回收原理(Go 1.5+ 并发标记清除)

Go的GC是并发标记清除(CMS) 算法,核心目标是低延迟,分为5个阶段:

阶段1:STW - 初始标记(Initial Mark)
  • 行为 :暂停所有Goroutine(STW,Stop The World),标记根对象(如全局变量、Goroutine栈上的对象);
  • 特点:耗时极短(通常微秒级)。
阶段2:并发标记(Concurrent Mark)
  • 行为:恢复Goroutine运行,后台GC线程并发遍历堆对象,标记所有可达对象;
  • 辅助:Goroutine分配内存时会触发"写屏障",记录对象引用的变化,保证标记的准确性。
阶段3:STW - 终止标记(Mark Termination)
  • 行为:再次暂停所有Goroutine,处理并发标记期间的竞争条件(如标记遗漏的对象);
  • 特点:耗时短,通常微秒级。
阶段4:并发清除(Concurrent Sweep)
  • 行为:恢复Goroutine运行,后台GC线程并发清理未标记的对象(释放内存);
  • 特点:不影响业务Goroutine,清理后的内存会被标记为空闲,供后续分配。
阶段5:并发清理(Concurrent Scavenge)
  • 行为:GC线程将空闲内存归还给操作系统(可选,根据内存使用情况触发);
  • 特点:避免进程占用过多物理内存。
核心优化:
  • 写屏障:并发标记时记录对象引用变化,保证标记准确性;
  • 分代标记(Go 1.19+):优先标记新分配的对象(存活时间短),提升标记效率;
  • 低延迟优先:STW阶段耗时控制在毫秒级甚至微秒级,适合高并发服务。

Go 写屏障(Write Barrier)

写屏障是 Go GC 在并发标记阶段保证标记准确性的核心机制------当 Goroutine 并发修改对象引用时,写屏障会记录引用的变化,避免 GC 遗漏可达对象(解决"并发标记时对象引用被修改"的竞争问题)。

核心作用:

在并发标记阶段,Goroutine 仍在运行并修改对象的引用关系,写屏障可以拦截所有"对象引用更新"操作,将新引用的对象标记为"可达",确保 GC 不会错误地回收仍在使用的对象。

Go 写屏障的类型(演进):

Go 经历了两次写屏障的优化,目前默认使用 混合写屏障(Go 1.8+)。

(1)Dijkstra 写屏障(Go 1.5-1.7)
  • 规则 :当修改对象 a 的字段,将其引用从 b 改为 c 时,标记新对象 c 为可达
  • 问题:需要额外标记栈上的对象,导致 STW 时间较长。
(2)混合写屏障(Go 1.8+,当前默认)

结合了 Dijkstra 写屏障和 Yuasa 写屏障的优点,无需 STW 扫描栈,进一步降低延迟。

  • 规则
    1. 当修改对象 a 的字段时,标记旧对象 b 为可达
    2. 当 Goroutine 创建新对象时,直接标记新对象为可达
    3. 当 Goroutine 从栈上读取对象并赋值给堆对象时,标记该栈对象为可达
  • 优势:并发标记阶段无需暂停 Goroutine 扫描栈,STW 时间大幅缩短。
写屏障的触发时机:

仅在 GC 并发标记阶段 启用写屏障;GC 其他阶段(如初始标记、清除)不会触发写屏障,避免不必要的性能开销。

总结:

写屏障是 Go GC 实现"并发标记"的关键技术,通过拦截对象引用的修改操作,保证在 Goroutine 并发运行时,GC 仍能准确标记所有可达对象,最终实现低延迟的并发垃圾回收。

相关推荐
b***666137 分钟前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
i***118639 分钟前
springboot使用redis
spring boot·redis·后端
froginwe111 小时前
Go 语言结构体
开发语言
aiopencode1 小时前
苹果应用商店上架的系统逻辑,从产品开发到使用 开心上架 上架IPA 交付审核流程
后端
松涛和鸣1 小时前
DAY20 Optimizing VS Code for C/C++ Development on Ubuntu
linux·c语言·开发语言·c++·嵌入式硬件·ubuntu
h***38181 小时前
SpringBoot - Cookie & Session 用户登录及登录状态保持功能实现
java·spring boot·后端
田里的水稻1 小时前
Python_编程中代码注释相关格式 PEP8 — Python 官方代码风格指南
开发语言·python
一只乔哇噻1 小时前
java后端工程师+AI大模型进修ing(研一版‖day57)
java·开发语言·人工智能·算法·语言模型
苏三说技术1 小时前
索引夺命10连问,你能顶住第几问?
后端