Files
interview/questions/13-Golang语言/Goroutine和并发模型.md
yasinshaw 7f3ab362b3 feat: rename Golang files to Chinese and supplement root files
Changes:
- Renamed all 10 Golang files from English to Chinese names
- Created 00-项目概述/项目概述.md with comprehensive project overview
- Created 08-算法与数据结构/算法与数据结构学习指南.md with detailed learning guide
- Created 12-面试技巧/面试准备进度.md with progress tracking
- Added .obsidian configuration for better markdown editing
- Updated Claude.MD with Chinese filename rule

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

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

391 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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、批处理