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>
7.2 KiB
7.2 KiB
Goroutine 和并发模型
问题
- 什么是 Goroutine?与线程的区别?
- 如何优雅地关闭 Goroutine?
- Channel 的底层实现原理是什么?
- select 的作用是什么?
- context 包的作用是什么?
- 如何避免 Goroutine 泄漏?
- 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 的特性
- 随机选择:多个 case 就绪时随机选择
- 阻塞:所有 case 不就绪时阻塞
- default:添加 default 后,select 不阻塞
ch := make(chan int)
select {
case val := <-ch:
fmt.Println(val)
default:
fmt.Println("no data")
}
5. context 包
作用
- 超时控制
- 取消信号传递
- 元数据传递
使用示例
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)
}()
// 如果没有数据发送到 ch,Goroutine 永久泄漏
}
解决方案
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 调度模型
组成
- G(Goroutine):用户级线程(轻量级)
- M(Machine):系统线程(与内核线程 1:1)
- P(Processor):逻辑处理器(M 的队列,运行 Go 代码)
GMP 模型
P1 (Processor) P2 (Processor) P3 (Processor)
│ │ │
├─ G1 ├─ G5 ├─ G9
├─ G2 ├─ G6 ├─ G10
└─ G3 └─ G7 └─ G11
│ │ │
↓ ↓ ↓
M1 (Thread) M2 (Thread) M3 (Thread)
工作流程:
- P 维护一个 Goroutine 队列
- P 从队列中取出 G,执行
- G 阻塞(I/O、channel)时,P 继续执行其他 G
- P 空闲时,从其他 P "偷" G(工作窃取)
调度策略
- Work-Stealing(工作窃取):P 从其他 P 窃取 G
- 抢占式调度:sysmon 线程抢占长时间运行的 G
- Time Slice(时间片):每个 G 执行 10ms 后调度
8. 阿里 P7 加分项
深度理解:
- 理解 Go 调度器的实现原理(GMP 模型)
- 理解 Channel 的底层实现(环形队列、锁)
- 理解 context 的传播机制
实战经验:
- 有处理 Goroutine 泄漏的经验
- 有处理并发竞争条件的经验
- 有性能优化的经验(减少锁、使用 sync.Pool)
性能优化:
- 理解如何避免 Goroutine 泄漏
- 理解如何减少 Channel 阻塞
- 理解如何提高并发性能(Worker Pool、批处理)