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>
This commit is contained in:
yasinshaw
2026-03-01 00:16:36 +08:00
parent 0e46a367c4
commit ab3a99f131
10 changed files with 2854 additions and 0 deletions

View File

@@ -0,0 +1,390 @@
# 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**
```go
func hello(name string) {
fmt.Println("Hello,", name)
}
func main() {
// 启动 Goroutine
go hello("Alice")
go hello("Bob")
// 等待(防止主 Goroutine 退出)
time.Sleep(time.Second)
}
```
---
### 2. 优雅关闭 Goroutine
#### **问题示例**
```go
// ❌ 无法停止
func worker() {
for {
fmt.Println("working...")
time.Sleep(time.Second)
}
}
func main() {
go worker()
time.Sleep(3 * time.Second)
// 程序退出worker 未清理
}
```
---
#### **解决方案:使用 channel**
```go
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 结构**
```go
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**
```go
ch := make(chan int)
// 发送会阻塞,直到有接收者
go func() {
ch <- 1 // 阻塞
}()
<-ch // 接收
```
**2. 有缓冲 Channel**
```go
ch := make(chan int, 10) // 缓冲大小 10
// 缓冲未满时,发送不阻塞
ch <- 1
ch <- 2
```
---
### 4. select 的作用
#### **多路复用**
```go
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 不阻塞
```go
ch := make(chan int)
select {
case val := <-ch:
fmt.Println(val)
default:
fmt.Println("no data")
}
```
---
### 5. context 包
#### **作用**
1. **超时控制**
2. **取消信号传递**
3. **元数据传递**
---
#### **使用示例**
**1. 超时控制**
```go
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. 取消信号**
```go
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. 元数据传递**
```go
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 泄漏
#### **问题示例**
```go
// ❌ Goroutine 泄漏
func query() {
ch := make(chan int)
go func() {
val := <-ch // 永久阻塞
fmt.Println(val)
}()
// 如果没有数据发送到 chGoroutine 永久泄漏
}
```
---
#### **解决方案**
**1. 使用 context**
```go
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**
```go
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、批处理