golang常见面试题总结

今日面试遇到golang相关的问题,所以让ai总结整理了 Go 语言常见面试题,涵盖基础、并发、内存管理、标准库等核心知识点。


一、基础语法与特性

1. Go 与 Python/Java 的核心区别

特性 Go Python Java
类型系统 静态类型,编译型 动态类型,解释型 静态类型,编译型
并发模型 Goroutine + Channel 线程/协程(asyncio) 线程/线程池
内存管理 GC,但可手动优化 GC GC
编译速度 极快 无需编译 较慢
部署 单二进制文件 依赖解释器 JRE + jar
错误处理 显式 error 返回值 异常 try/except 异常 try/catch

2. 值传递 vs 引用传递

go 复制代码
// Go 只有值传递!但指针、slice、map、channel 是引用类型

func modifyValue(x int) {
    x = 100  // 修改的是副本,不影响原值
}

func modifyPointer(x *int) {
    *x = 100  // 修改指针指向的值,影响原值
}

func modifySlice(s []int) {
    s[0] = 100  // slice 是引用类型,会影响原值!
    s = append(s, 200)  // 但 append 可能触发扩容,此时 s 指向新数组,不影响原值
}

// 面试陷阱题
func main() {
    s := []int{1, 2, 3}
    modifySlice(s)
    fmt.Println(s)  // 输出 [100 2 3],不是 [1 2 3]!
}

3. defer 的执行顺序和陷阱

go 复制代码
// defer 是 LIFO(后进先出)
func main() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    // 输出: 3 2 1
}

// 陷阱:defer 参数在定义时求值
func trap() {
    i := 0
    defer fmt.Println(i)  // 输出 0,不是 3!
    i = 3
}

// 陷阱:defer 与 return 的执行顺序
func deferAndReturn() (result int) {
    defer func() {
        result++  // 可以修改命名返回值!
    }()
    return 0  // 实际返回 1
}

// 性能陷阱:循环内 defer
func bad() {
    for i := 0; i < 10000; i++ {
        defer fmt.Println(i)  // 内存泄漏!defer 不会立即执行
    }
}

二、并发编程(重点)

4. Goroutine 和 Channel

go 复制代码
// Goroutine 是用户态轻量级线程,~2KB栈,可动态增长
// 由 Go runtime 调度,非 OS 线程

// Channel 是 Goroutine 间通信的方式(CSP 模型)

// 基础用法
func basicChannel() {
    ch := make(chan int, 3)  // 缓冲通道,容量3
    
    go func() {
        ch <- 1  // 发送
        ch <- 2
        ch <- 3
        close(ch)  // 关闭通道
    }()
    
    for v := range ch {  // 遍历直到关闭
        fmt.Println(v)
    }
}

// 面试常考:无缓冲 vs 有缓冲
func bufferedVsUnbuffered() {
    // 无缓冲:同步通信,发送和接收必须同时准备好
    unbuf := make(chan int)
    go func() { unbuf <- 1 }()  // 发送会阻塞,直到有人接收
    <-unbuf  // 接收会阻塞,直到有人发送
    
    // 有缓冲:异步通信,满时发送阻塞,空时接收阻塞
    buf := make(chan int, 1)
    buf <- 1  // 不阻塞,立即返回
    buf <- 2  // 阻塞!缓冲区满
}

5. select 多路复用

go 复制代码
func selectDemo() {
    ch1 := make(chan int)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- 1
    }()
    
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "hello"
    }()
    
    // 同时监听多个通道
    select {
    case v1 := <-ch1:
        fmt.Println("ch1:", v1)
    case v2 := <-ch2:
        fmt.Println("ch2:", v2)
    case <-time.After(3 * time.Second):  // 超时
        fmt.Println("timeout")
    default:  // 非阻塞,立即执行
        fmt.Println("no data")
    }
    
    // 面试常考:select 的随机性
    // 当多个 case 同时就绪时,select 随机选择一个!
}

6. 并发同步原语

go 复制代码
import "sync"

// WaitGroup:等待一组 Goroutine 完成
func waitGroupDemo() {
    var wg sync.WaitGroup
    
    for i := 0; i < 3; i++ {
        wg.Add(1)  // 增加计数
        go func(id int) {
            defer wg.Done()  // 完成时减少计数
            fmt.Printf("Worker %d done\n", id)
        }(i)
    }
    
    wg.Wait()  // 阻塞直到计数为0
}

// Mutex:互斥锁
func mutexDemo() {
    var mu sync.Mutex
    var count int
    
    for i := 0; i < 1000; i++ {
        go func() {
            mu.Lock()
            count++  // 临界区
            mu.Unlock()
        }()
    }
}

// RWMutex:读写锁,读多写少场景
type Cache struct {
    mu    sync.RWMutex
    data  map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()  // 读锁,多个读并发
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()  // 写锁,独占
    defer c.mu.Unlock()
    c.data[key] = value
}

// Once:只执行一次(单例模式)
var (
    instance *Singleton
    once     sync.Once
)

func GetInstance() *Singleton {
    once.Do(func() {
        instance = &Singleton{}
    })
    return instance
}

// Pool:对象池,减少 GC 压力
var bytePool = sync.Pool{
    New: func() interface{} {
        return make([]byte, 1024)
    },
}

func usePool() {
    buf := bytePool.Get().([]byte)  // 获取
    // 使用...
    bytePool.Put(buf)  // 归还,复用
}

7. Context 上下文控制

go 复制代码
// Context 是 Go 并发的灵魂,用于传递取消信号、超时、元数据

func contextDemo() {
    // 1. 超时控制
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()
    
    select {
    case <-time.After(5 * time.Second):
        fmt.Println("work done")
    case <-ctx.Done():
        fmt.Println("timeout:", ctx.Err())  // context deadline exceeded
    }
    
    // 2. 手动取消
    ctx2, cancel2 := context.WithCancel(context.Background())
    go func() {
        time.Sleep(1 * time.Second)
        cancel2()  // 通知所有监听 ctx2.Done() 的 Goroutine 退出
    }()
    
    // 3. 传递元数据
    ctx3 := context.WithValue(context.Background(), "userID", "123")
    userID := ctx3.Value("userID").(string)
}

// 面试常考:Context 传递最佳实践
func handleRequest(ctx context.Context, req *Request) {
    // 每个需要异步处理的函数都接收 ctx 作为第一个参数
    // 形成树状结构,根 Context 取消时,所有子 Context 都收到信号
    
    go queryDatabase(ctx, req.Query)
    go callExternalAPI(ctx, req.API)
    // 两者都会响应 ctx 的取消信号
}

8. 常见并发陷阱

go 复制代码
// 陷阱1:闭包捕获循环变量
func closureTrap() {
    for i := 0; i < 10; i++ {
        go func() {
            fmt.Println(i)  // 错误!所有 Goroutine 打印相同的值(大概率是10)
        }()
    }
    
    // 正确:传参
    for i := 0; i < 10; i++ {
        go func(id int) {
            fmt.Println(id)  // 正确
        }(i)
    }
}

// 陷阱2:向已关闭的 Channel 发送
func closedChannelTrap() {
    ch := make(chan int)
    close(ch)
    ch <- 1  // panic: send on closed channel
}

// 陷阱3:Channel 泄露
func leakTrap() {
    ch := make(chan int)
    go func() {
        // 如果这里 panic 或提前返回,无人接收,发送方永远阻塞
        ch <- 1
    }()
    // 忘记 <-ch
}

// 陷阱4:死锁
func deadlockTrap() {
    ch := make(chan int)  // 无缓冲
    ch <- 1  // 死锁!无接收方,永远阻塞
    <-ch
}

三、内存管理与 GC

9. 逃逸分析

go 复制代码
// 逃逸分析:编译器决定变量分配在栈还是堆

func stackAlloc() int {
    x := 10  // 栈分配,函数返回后自动释放
    return x
}

func heapAlloc() *int {
    x := 10
    return &x  // 逃逸到堆!函数返回后仍需访问
}

// 查看逃逸分析:go build -gcflags="-m"

// 常见逃逸场景:
// 1. 返回指针
// 2. 闭包引用外部变量
// 3. 向 interface{} 传值
// 4. 切片/Map 扩容超过栈容量

10. GC 机制

go 复制代码
// Go 使用三色标记 + 混合写屏障的并发 GC

// GC 调优参数
// GOGC=100  默认,堆增长100%触发GC
// GOGC=off  关闭GC(仅用于调试)

// 减少 GC 压力的技巧
func reduceGCPressure() {
    // 1. 对象池(见 sync.Pool)
    
    // 2. 预分配切片容量,避免频繁扩容
    data := make([]int, 0, 10000)  // 预分配
    
    // 3. 大对象使用指针,避免值拷贝
    type BigStruct struct {
        data [1024 * 1024]int  // 1MB
    }
    
    func process(ptr *BigStruct) {  // 传指针,8字节
        // 而不是 process(s BigStruct),拷贝1MB
    }
}

四、接口与反射

11. 接口实现机制

go 复制代码
// Go 接口是隐式实现,鸭子类型

type Reader interface {
    Read(p []byte) (n int, err error)
}

// 不需要声明 implements,只要有 Read 方法就自动实现
type MyReader struct{}

func (m MyReader) Read(p []byte) (n int, err error) {
    return 0, nil
}

// 面试常考:空接口 interface{}
func emptyInterface() {
    var i interface{} = "hello"
    
    // 类型断言
    s := i.(string)  // 失败时 panic
    
    s, ok := i.(string)  // 安全断言,ok=false 时不 panic
    
    // 类型 switch
    switch v := i.(type) {
    case string:
        fmt.Println("string:", v)
    case int:
        fmt.Println("int:", v)
    default:
        fmt.Println("unknown:", v)
    }
}

// 面试常考:nil 接口
type MyError struct{}

func (e *MyError) Error() string { return "error" }

func returnNilError() error {
    var e *MyError = nil
    return e  // 返回的是 (type=*MyError, value=nil) 的接口,不是 nil!
}

func main() {
    err := returnNilError()
    fmt.Println(err == nil)  // false!陷阱!
}

12. 反射

go 复制代码
import "reflect"

func reflectDemo() {
    type User struct {
        Name string `json:"name"`
        Age  int    `json:"age"`
    }
    
    u := User{Name: "Tom", Age: 20}
    t := reflect.TypeOf(u)
    v := reflect.ValueOf(u)
    
    // 遍历字段
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := v.Field(i)
        fmt.Printf("%s: %v (tag: %s)\n", field.Name, value, field.Tag.Get("json"))
    }
    
    // 修改值(必须传指针)
    v2 := reflect.ValueOf(&u).Elem()
    v2.FieldByName("Age").SetInt(30)
}

// 反射的性能代价:比直接调用慢 10-100 倍
// 生产环境尽量避免在热路径使用反射

五、标准库与工程实践

13. HTTP 服务

go 复制代码
package main

import (
    "net/http"
    "time"
)

// 标准库实现
func basicHTTPServer() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)  // 默认无超时,生产勿用!
}

// 生产级配置
func productionHTTPServer() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", handler)
    
    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
        MaxHeaderBytes: 1 << 20,  // 1MB
    }
    
    server.ListenAndServe()
}

// 中间件模式
func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL, time.Since(start))
    })
}

14. 测试

go 复制代码
// 单元测试
func Add(a, b int) int {
    return a + b
}

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d, want 5", result)
    }
}

// 表驱动测试(Go 惯用法)
func TestAddTable(t *testing.T) {
    tests := []struct {
        a, b, want int
    }{
        {2, 3, 5},
        {-1, 1, 0},
        {0, 0, 0},
    }
    
    for _, tt := range tests {
        got := Add(tt.a, tt.b)
        if got != tt.want {
            t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
        }
    }
}

// Benchmark
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}
// go test -bench=.

// Mock 接口
type Database interface {
    Get(id string) (User, error)
}

type MockDB struct {
    data map[string]User
}

func (m *MockDB) Get(id string) (User, error) {
    return m.data[id], nil
}

六、高频面试题速查

简答题

问题 核心要点
Goroutine 为什么轻量? 用户态调度,~2KB栈,动态增长,非OS线程
Channel 底层实现? 环形队列 + 锁 + 发送/接收队列
Go 的 map 是线程安全的吗? 不是!需用 sync.RWMutex 或 sync.Map
什么情况下会发生内存泄漏? Goroutine 泄露、未关闭的 Channel、全局变量累积
defer 的用途和陷阱? 资源清理、参数求值时机、循环内使用
如何实现单例模式? sync.Once、init、原子操作
两个 Goroutine 如何交替打印? 两个无缓冲 Channel 互相通知

代码题

go 复制代码
// 题1:交替打印数字和字母
// 输出: 1A2B3C4D...

func alternatePrint() {
    numCh := make(chan bool)
    charCh := make(chan bool)
    done := make(chan bool)
    
    go func() {
        for i := 1; i <= 4; i++ {
            <-numCh  // 等待信号
            fmt.Print(i)
            charCh <- true  // 通知打印字母
        }
    }()
    
    go func() {
        for c := 'A'; c <= 'D'; c++ {
            <-charCh
            fmt.Printf("%c", c)
            numCh <- true
        }
        done <- true
    }()
    
    numCh <- true  // 启动
    <-done
}

// 题2:实现一个线程安全的 Map
type SafeMap struct {
    mu   sync.RWMutex
    data map[string]interface{}
}

func (s *SafeMap) Get(key string) (interface{}, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    val, ok := s.data[key]
    return val, ok
}

func (s *SafeMap) Set(key string, val interface{}) {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.data[key] = val
}

// 题3:控制并发数量(信号量模式)
func limitedConcurrency(urls []string, maxConcurrent int) {
    sem := make(chan struct{}, maxConcurrent)
    var wg sync.WaitGroup
    
    for _, url := range urls {
        wg.Add(1)
        sem <- struct{}{}  // 获取信号量
        
        go func(u string) {
            defer wg.Done()
            defer func() { <-sem }()  // 释放信号量
            
            fetch(u)
        }(url)
    }
    
    wg.Wait()
}

学习资源推荐

资源 说明
《Go 程序设计语言》 经典入门,Kernighan 著
《Go 语言高级编程》 深入 runtime、CGO
Go 官方博客 golang.org/blog,GC、调度器详解
Go 101 go101.org,深入语言细节
Uber Go Style Guide 工程实践规范

相关推荐
AI淇橦学4 小时前
零基础学 Agent :拆解一个 Agent 的「零件清单」——8 个模块逐一讲透 第 2 期
面试
Lee川4 小时前
时空迷宫探险记:从O(1)到O(2^n)的算法进化论
算法·面试
前端Hardy5 小时前
别再手动调 Prompt 了!这款开源神器让 AI 输出质量提升 300%,支持 Claude、GPT、Gemini,还免费开源!
前端·javascript·面试
yuhaiqiang5 小时前
谈谈什么是多AI交叉论证思维
前端·后端·面试
加洛斯5 小时前
JAVA知识梳理:一文搞懂集合中的List与ArrayList的基础与进阶
java·后端·面试
发现一只大呆瓜5 小时前
深度拆解 fetch-event-source库实现原理
前端·javascript·面试
前端Hardy5 小时前
为什么资深前端都在悄悄学 WebAssembly?
前端·javascript·面试
发现一只大呆瓜5 小时前
SSE 流式传输:中断超时处理
前端·javascript·面试
皙然6 小时前
深入理解 Java HashMap:从底层原理、源码设计到面试考点全解析
java·开发语言·面试