Files
interview/questions/13-Golang语言/go-goroutine.md
yasinshaw ab3a99f131 feat: add 10 Golang interview questions
Added comprehensive Golang interview preparation materials:
- 基础语法(值类型、切片、map、defer、接口、struct、new/make)
- Goroutine 和并发模型(与线程对比、调度模型、内存模型)
- 错误处理和测试(error、panic/recover、单元测试、Benchmark)
- 并发编程进阶(Mutex、RWMutex、WaitGroup、atomic、数据竞争)
- HTTP 和 Web 开发(Client、Server、中间件模式)
- 内存模型和垃圾回收(内存分配、逃逸分析、GC)
- 性能优化(pprof、内存优化、CPU优化、并发优化)
- 反射和 unsafe(反射性能、unsafe 使用场景)
- 接口和类型系统(类型断言、interface{}、类型嵌入、泛型)
- 数据库操作(database/sql、GORM、事务、SQL 注入防护)
- 项目结构和工程化(标准项目结构、Go Module、CI/CD)

Each file includes:
- Detailed questions and comprehensive answers
- Code examples and best practices
- Alibaba P7 level requirements

Total: 60 interview questions (50 backend + 10 Golang)

Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-03-01 00:16:36 +08:00

7.2 KiB
Raw Blame History

Goroutine 和并发模型

问题

  1. 什么是 Goroutine与线程的区别
  2. 如何优雅地关闭 Goroutine
  3. Channel 的底层实现原理是什么?
  4. select 的作用是什么?
  5. context 包的作用是什么?
  6. 如何避免 Goroutine 泄漏?
  7. Go 的调度器GMP 模型)是怎样的?

标准答案

1. Goroutine vs 线程

对比

特性 Goroutine 线程
内存占用 2 KB初始 1-2 MB
创建成本 极低 较高
切换成本 低(用户态) 高(内核态)
数量 数十万+ 数千个
调度 Go 调度器GMP 操作系统
动态伸缩(按需增长) 固定大小

创建 Goroutine

func hello(name string) {
    fmt.Println("Hello,", name)
}

func main() {
    // 启动 Goroutine
    go hello("Alice")
    go hello("Bob")

    // 等待(防止主 Goroutine 退出)
    time.Sleep(time.Second)
}

2. 优雅关闭 Goroutine

问题示例

// ❌ 无法停止
func worker() {
    for {
        fmt.Println("working...")
        time.Sleep(time.Second)
    }
}

func main() {
    go worker()
    time.Sleep(3 * time.Second)
    // 程序退出worker 未清理
}

解决方案:使用 channel

func worker(stop <-chan struct{}) {
    for {
        select {
        case <-stop:
            fmt.Println("worker stopping...")
            return
        default:
            fmt.Println("working...")
            time.Sleep(time.Second)
        }
    }
}

func main() {
    stop := make(chan struct{})

    go worker(stop)

    time.Sleep(3 * time.Second)
    close(stop)  // 发送停止信号
    time.Sleep(time.Second)
}

3. Channel 底层实现

Channel 结构

type hchan struct {
    qcount   uint           // 队列中的数据量
    dataqsiz uint           // 环形队列大小
    buf      unsafe.Pointer  // 环形队列指针
    elemsize uint16          // 元素大小
    closed   uint32          // 是否关闭
    elemtype *_type         // 元素类型
    sendx    uint64         // 发送索引
    recvx    uint64         // 接收索引
    recvq    waitq          // 接收等待队列
    sendq    waitq          // 发送等待队列
    lock     mutex          // 互斥锁
}

Channel 类型

1. 无缓冲 Channel

ch := make(chan int)

// 发送会阻塞,直到有接收者
go func() {
    ch <- 1  // 阻塞
}()

<-ch  // 接收

2. 有缓冲 Channel

ch := make(chan int, 10)  // 缓冲大小 10

// 缓冲未满时,发送不阻塞
ch <- 1
ch <- 2

4. select 的作用

多路复用

func worker1(ch chan<- int) {
    time.Sleep(1 * time.Second)
    ch <- 1
}

func worker2(ch chan<- int) {
    time.Sleep(2 * time.Second)
    ch <- 2
}

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go worker1(ch1)
    go worker2(ch2)

    select {
    case val := <-ch1:
        fmt.Println("worker1:", val)
    case val := <-ch2:
        fmt.Println("worker2:", val)
    case <-time.After(3 * time.Second):
        fmt.Println("timeout")
    }
}

select 的特性

  1. 随机选择:多个 case 就绪时随机选择
  2. 阻塞:所有 case 不就绪时阻塞
  3. default:添加 default 后select 不阻塞
ch := make(chan int)

select {
case val := <-ch:
    fmt.Println(val)
default:
    fmt.Println("no data")
}

5. context 包

作用

  1. 超时控制
  2. 取消信号传递
  3. 元数据传递

使用示例

1. 超时控制

func doWork(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("work cancelled:", ctx.Err())
            return
        default:
            fmt.Println("working...")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    go doWork(ctx)

    time.Sleep(3 * time.Second)
}

2. 取消信号

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    go func() {
        time.Sleep(1 * time.Second)
        cancel()  // 取消
    }()

    select {
    case <-ctx.Done():
        fmt.Println("cancelled:", ctx.Err())
    }
}

3. 元数据传递

func handler(ctx context.Context) {
    userID := ctx.Value("userID").(string)
    fmt.Println("userID:", userID)
}

func main() {
    ctx := context.WithValue(context.Background(), "userID", "12345")
    handler(ctx)
}

6. 避免 Goroutine 泄漏

问题示例

// ❌ Goroutine 泄漏
func query() {
    ch := make(chan int)
    go func() {
        val := <-ch  // 永久阻塞
        fmt.Println(val)
    }()
    // 如果没有数据发送到 chGoroutine 永久泄漏
}

解决方案

1. 使用 context

func query(ctx context.Context) {
    ch := make(chan int)

    go func() {
        select {
        case val := <-ch:
            fmt.Println(val)
        case <-ctx.Done():
            return  // 超时或取消时退出
        }
    }()

    select {
    case ch <- 1:
    case <-time.After(1 * time.Second):
        fmt.Println("timeout")
    }
}

2. 使用缓冲 Channel

func query() {
    ch := make(chan int, 1)  // 缓冲大小 1

    go func() {
        val := <-ch
        fmt.Println(val)
    }()

    ch <- 1  // 不会阻塞
}

7. GMP 调度模型

组成

  • GGoroutine:用户级线程(轻量级)
  • MMachine:系统线程(与内核线程 1:1
  • PProcessor逻辑处理器M 的队列,运行 Go 代码)

GMP 模型

P1 (Processor)    P2 (Processor)    P3 (Processor)
    │                   │                   │
    ├─ G1               ├─ G5               ├─ G9
    ├─ G2               ├─ G6               ├─ G10
    └─ G3               └─ G7               └─ G11
    │                   │                   │
    ↓                   ↓                   ↓
M1 (Thread)        M2 (Thread)        M3 (Thread)

工作流程

  1. P 维护一个 Goroutine 队列
  2. P 从队列中取出 G执行
  3. G 阻塞I/O、channelP 继续执行其他 G
  4. P 空闲时,从其他 P "偷" G工作窃取

调度策略

  1. Work-Stealing工作窃取P 从其他 P 窃取 G
  2. 抢占式调度sysmon 线程抢占长时间运行的 G
  3. Time Slice时间片:每个 G 执行 10ms 后调度

8. 阿里 P7 加分项

深度理解

  • 理解 Go 调度器的实现原理GMP 模型)
  • 理解 Channel 的底层实现(环形队列、锁)
  • 理解 context 的传播机制

实战经验

  • 有处理 Goroutine 泄漏的经验
  • 有处理并发竞争条件的经验
  • 有性能优化的经验(减少锁、使用 sync.Pool

性能优化

  • 理解如何避免 Goroutine 泄漏
  • 理解如何减少 Channel 阻塞
  • 理解如何提高并发性能Worker Pool、批处理