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>
This commit is contained in:
390
questions/13-Golang语言/Goroutine和并发模型.md
Normal file
390
questions/13-Golang语言/Goroutine和并发模型.md
Normal 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)
|
||||
}()
|
||||
// 如果没有数据发送到 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、批处理)
|
||||
Reference in New Issue
Block a user