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>
391 lines
7.2 KiB
Markdown
391 lines
7.2 KiB
Markdown
# 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)
|
||
}()
|
||
// 如果没有数据发送到 ch,Goroutine 永久泄漏
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **解决方案**
|
||
|
||
**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 调度模型
|
||
|
||
#### **组成**
|
||
|
||
- **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)
|
||
```
|
||
|
||
**工作流程**:
|
||
1. P 维护一个 Goroutine 队列
|
||
2. P 从队列中取出 G,执行
|
||
3. G 阻塞(I/O、channel)时,P 继续执行其他 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、批处理)
|