diff --git a/questions/13-Golang语言/go-basic-syntax.md b/questions/13-Golang语言/go-basic-syntax.md new file mode 100644 index 0000000..c5d93f7 --- /dev/null +++ b/questions/13-Golang语言/go-basic-syntax.md @@ -0,0 +1,453 @@ +# Golang 基础语法 + +## 问题 + +1. Go 的值类型和引用类型有哪些? +2. Go 的切片和数组有什么区别? +3. Go 的 map 是线程安全的吗?如何实现线程安全? +4. Go 的 defer 执行顺序和作用是什么? +5. Go 的接口(interface)是如何实现的?鸭子类型是什么? +6. Go 的 struct 可以比较吗?什么时候可以比较,什么时候不可以? +7. Go 的 new 和 make 有什么区别? + +--- + +## 标准答案 + +### 1. 值类型 vs 引用类型 + +#### **值类型** +- 基本类型:`int`、`float`、`bool`、`string` +- 数组:`[n]int` +- 结构体:`struct` + +**特点**:直接存储值,拷贝时复制整个值 + +#### **引用类型** +- 切片:`[]T` +- 映射:`map[K]V` +- 通道:`chan T` +- 接口:`interface{}` +- 指针:`*T` + +**特点**:存储引用地址,拷贝时只复制引用 + +**示例**: +```go +// 值类型 +a := 1 +b := a // 拷贝值 +b = 2 +fmt.Println(a) // 1(不受影响) + +// 引用类型 +s1 := []int{1, 2, 3} +s2 := s1 // 拷贝引用(指向同一个底层数组) +s2[0] = 999 +fmt.Println(s1) // [999 2 3](受影响) +``` + +--- + +### 2. 切片 vs 数组 + +#### **数组(Array)** + +```go +// 数组:固定长度 +var arr [5]int // 长度为 5 的数组 +arr := [3]int{1, 2, 3} +arr2 := [...]int{1, 2, 3} // 自动推导长度 + +// 数组是值类型 +arr3 := arr +arr3[0] = 999 +fmt.Println(arr) // [1 2 3](不受影响) +``` + +**特点**: +- 长度固定(类型的一部分) +- 值类型(拷贝时复制整个数组) +- 很少直接使用 + +--- + +#### **切片(Slice)** + +```go +// 切片:动态长度 +s := []int{1, 2, 3} +s = append(s, 4) // 动态追加 + +// 切片的三部分 +// 1. 指针:指向底层数组 +// 2. 长度:当前元素个数 +// 3. 容量:底层数组的总大小 + +// 切片的底层结构 +type slice struct { + array unsafe.Pointer // 指向底层数组 + len int // 长度 + cap int // 容量 +} +``` + +**切片扩容机制**: +```go +s := make([]int, 0, 3) // len=0, cap=3 +s = append(s, 1) // len=1, cap=3 +s = append(s, 2) // len=2, cap=3 +s = append(s, 3) // len=3, cap=3 +s = append(s, 4) // len=4, cap=6(扩容!容量翻倍) +``` + +**扩容策略**: +- 容量 < 1024:翻倍 +- 容量 ≥ 1024:增长 1.25 倍 + +--- + +### 3. Map 的线程安全问题 + +#### **Go 的 map 不是线程安全的** + +```go +var m = make(map[int]int) + +// ❌ 并发写入会 panic +go func() { + m[1] = 1 +}() + +go func() { + m[2] = 2 +}() + +// panic: concurrent map writes +``` + +--- + +#### **解决方案 1:加锁** + +```go +import "sync" + +var ( + m = make(map[int]int) + mu sync.Mutex +) + +func write(key, value int) { + mu.Lock() + m[key] = value + mu.Unlock() +} + +func read(key int) int { + mu.Lock() + defer mu.Unlock() + return m[key] +} +``` + +--- + +#### **解决方案 2:sync.Map** + +**适用场景**: +- 读多写少 +- Key 的集合稳定(少量增删) + +```go +var m sync.Map + +// 写入 +m.Store(1, "one") +m.Store(2, "two") + +// 读取 +if val, ok := m.Load(1); ok { + fmt.Println(val.(string)) +} + +// 删除 +m.Delete(1) + +// 遍历 +m.Range(func(key, value interface{}) bool { + fmt.Println(key, value) + return true // 继续遍历 +}) +``` + +**sync.Map 底层实现**: +- `read`:只读 map(无锁,atomic) +- `dirty`:读写 map(加锁) +- `misses`:统计 read 未命中次数,达到阈值时提升 dirty 到 read + +--- + +### 4. defer 的执行顺序 + +#### **特点** + +1. **defer 后进先出(LIFO)** +2. **在函数返回前执行** +3. **可以修改返回值** + +--- + +#### **示例 1:执行顺序** + +```go +func example() { + defer fmt.Println("defer 1") + defer fmt.Println("defer 2") + defer fmt.Println("defer 3") + + fmt.Println("main") +} + +// 输出: +// main +// defer 3 +// defer 2 +// defer 1 +``` + +--- + +#### **示例 2:修改返回值** + +```go +func add(a, b int) (result int) { + defer func() { + result += 10 // 修改返回值 + }() + + result = a + b + return result +} + +func main() { + r := add(1, 2) + fmt.Println(r) // 13(1 + 2 + 10) +} +``` + +--- + +#### **示例 3:defer 与 panic** + +```go +func example() { + defer func() { + if err := recover(); err != nil { + fmt.Println("recover:", err) + } + }() + + defer fmt.Println("defer 1") + + panic("error") + defer fmt.Println("defer 2") // 不会执行 +} + +// 输出: +// defer 1 +// recover: error +``` + +--- + +### 5. 接口(Interface) + +#### **接口的定义** + +```go +type Speaker interface { + Speak() string +} + +type Dog struct { + Name string +} + +func (d Dog) Speak() string { + return "汪汪" +} + +type Cat struct { + Name string +} + +func (c Cat) Speak() string { + return "喵喵" +} +``` + +--- + +#### **鸭子类型(Duck Typing)** + +```go +// Go 不需要显式声明实现了接口 +// 只要实现了接口的所有方法,就自动实现了该接口 + +func MakeSound(s Speaker) { + fmt.Println(s.Speak()) +} + +func main() { + dog := Dog{Name: "旺财"} + cat := Cat{Name: "咪咪"} + + MakeSound(dog) // 汪汪 + MakeSound(cat) // 喵喵 +} +``` + +--- + +#### **接口的零值** + +```go +var s Speaker +fmt.Println(s == nil) // true + +// 接口内部存储:(type, value) +// type = nil, value = nil 时,接口才等于 nil +``` + +--- + +#### **类型断言** + +```go +func checkType(i interface{}) { + switch v := i.(type) { + case int: + fmt.Println("int:", v) + case string: + fmt.Println("string:", v) + case Dog: + fmt.Println("Dog:", v.Name) + default: + fmt.Println("unknown type") + } +} +``` + +--- + +### 6. Struct 的比较 + +#### **可比较的 Struct** + +```go +type Point struct { + X int + Y int +} + +p1 := Point{X: 1, Y: 2} +p2 := Point{X: 1, Y: 2} +fmt.Println(p1 == p2) // true + +// 条件: +// 1. 所有字段都是可比较的 +// 2. 字段类型相同 +// 3. 字段值相同 +``` + +--- + +#### **不可比较的 Struct** + +```go +type Foo struct { + m map[string]int // map 不可比较 +} + +f1 := Foo{m: make(map[string]int)} +f2 := Foo{m: make(map[string]int)} +// f1 == f2 // 编译错误:map 不可比较 + +// 其他不可比较类型: +// - slice +// - map +// - function +// - 包含这些类型的 struct +``` + +--- + +### 7. new vs make + +#### **new** + +```go +// new: 分配内存,返回指针 +p1 := new(int) +fmt.Println(p1) // 0xc0000140a0(地址) +fmt.Println(*p1) // 0(零值) + +// 等价于 +var p2 int +fmt.Println(&p2) // 0xc0000140b0 +``` + +**适用**:所有类型(基本类型、结构体) + +--- + +#### **make** + +```go +// make: 只用于创建 slice、map、chan +// 返回初始化后的值(非指针) + +// 切片 +s := make([]int, 3, 5) // len=3, cap=5 + +// map +m := make(map[string]int) + +// channel +ch := make(chan int, 10) +``` + +**适用**: +- `slice` +- `map` +- `channel` + +--- + +#### **对比** + +| 特性 | new | make | +|------|-----|------| +| **返回类型** | 指针 | 值 | +| **适用类型** | 所有类型 | slice、map、chan | +| **初始化** | 零值 | 初始化后可用 | + +--- + +### 8. 阿里 P7 加分项 + +**深度理解**: +- 理解 slice 的底层实现(数组、指针、长度、容量) +- 理解 map 的扩容机制和哈希冲突解决 +- 理解 interface 的动态派发和类型断言 + +**实战经验**: +- 有处理并发 map 问题的经验 +- 有 defer 导致的性能问题(defer 循环中的大锁) +- 有接口设计的最佳实践 + +**性能优化**: +- 理解如何减少内存分配(对象池、sync.Pool) +- 理解如何优化 slice 的扩容(预分配容量) +- 理解如何减少锁竞争(分片锁、无锁编程) diff --git a/questions/13-Golang语言/go-concurrent-advanced.md b/questions/13-Golang语言/go-concurrent-advanced.md new file mode 100644 index 0000000..37bbd56 --- /dev/null +++ b/questions/13-Golang语言/go-concurrent-advanced.md @@ -0,0 +1,240 @@ +# Golang 并发编程进阶 + +## 问题 + +1. sync.Mutex 和 sync.RWMutex 的区别? +2. sync.WaitGroup 和 sync.Once 的使用场景? +3. atomic 包的作用是什么? +4. 如何实现一个无锁队列? +5. 如何检测数据竞争(Data Race)? +6. Go 的内存模型(Happens-Before)是什么? + +--- + +## 标准答案 + +### 1. Mutex vs RWMutex + +#### **Mutex(互斥锁)** + +```go +var mu sync.Mutex + +func write() { + mu.Lock() + defer mu.Unlock() + + // 写操作 +} +``` + +--- + +#### **RWMutex(读写锁)** + +```go +var mu sync.RWMutex + +func read() { + mu.RLock() + defer mu.RUnlock() + + // 读操作(多个 Goroutine 可以同时读) +} + +func write() { + mu.Lock() + defer mu.Unlock() + + // 写操作(独占访问) +} +``` + +--- + +**使用场景**: +- **Mutex**:读写都少 +- **RWMutex**:读多写少 + +--- + +### 2. WaitGroup 和 Once + +#### **WaitGroup** + +**用途**:等待一组 Goroutine 完成 + +```go +var wg sync.WaitGroup + +func worker(id int) { + defer wg.Done() + + fmt.Printf("Worker %d starting\n", id) + time.Sleep(time.Second) + fmt.Printf("Worker %d done\n", id) +} + +func main() { + for i := 1; i <= 3; i++ { + wg.Add(1) + go worker(i) + } + + wg.Wait() // 等待所有 worker 完成 + fmt.Println("All workers done") +} +``` + +--- + +#### **Once** + +**用途**:确保函数只执行一次 + +```go +var once sync.Once + +func initDB() { + once.Do(func() { + // 初始化数据库(只执行一次) + fmt.Println("DB initialized") + }) +} + +func main() { + initDB() + initDB() // 不会再次执行 +} +``` + +--- + +### 3. atomic 包 + +#### **作用** + +**原子操作**:无锁并发编程 + +--- + +#### **示例** + +```go +import "sync/atomic" + +var counter int64 + +// ❌ 非原子操作(数据竞争) +func increment() { + counter++ // 不是原子操作 +} + +// ✅ 原子操作 +func incrementAtomic() { + atomic.AddInt64(&counter, 1) +} + +// 读取 +value := atomic.LoadInt64(&counter) + +// 比较并交换(CAS) +old := atomic.LoadInt64(&counter) +atomic.CompareAndSwapInt64(&counter, old, new) +``` + +--- + +### 4. 检测数据竞争 + +#### **使用 go test -race** + +```bash +# 检测数据竞争 +go test -race ./... + +# 运行程序 +go run -race main.go +``` + +--- + +#### **示例** + +```go +// ❌ 有数据竞争 +func main() { + var counter int + go func() { + counter++ // 数据竞争 + }() + counter++ // 数据竞争 +} + +// ✅ 无数据竞争 +func main() { + var counter int64 + go func() { + atomic.AddInt64(&counter, 1) + }() + atomic.AddInt64(&counter, 1) +} +``` + +--- + +### 5. Happens-Before + +#### **定义** + +**Happens-Before**:保证一个操作对另一个操作可见 + +--- + +#### **规则** + +1. **Goroutine 创建**:`go func()` happens-before `func()` 开始 +2. **Channel**:发送 happens-before 接收 +3. **Lock**:Unlock happens-before 下一次 Lock + +--- + +#### **示例** + +```go +var a, b int + +func main() { + go func() { + a = 1 // ① + ch <- struct{}{} // ② + }() + + b = 2 // ③ + <-ch // ④ + + // 保证了: + // ① happens-before ② + // ② happens-before ④ + // 因此:a = 1 一定会在 b = 2 之前执行 +} +``` + +--- + +### 6. 阿里 P7 加分项 + +**深度理解**: +- 理解 Go 的内存模型(Happens-Before) +- 理解 atomic 包的实现原理(CAS) +- 理解 RWMutex 的实现(写锁饥饿问题) + +**实战经验**: +- 有使用 -race 检测数据竞争的经验 +- 有优化并发程序的经验(减少锁、使用 atomic) +- 有实现无锁数据结构的经验 + +**并发编程**: +- 理解如何避免数据竞争 +- 理解如何使用原子操作 +- 理解如何实现高性能并发程序 diff --git a/questions/13-Golang语言/go-database.md b/questions/13-Golang语言/go-database.md new file mode 100644 index 0000000..8b905d1 --- /dev/null +++ b/questions/13-Golang语言/go-database.md @@ -0,0 +1,225 @@ +# Golang 数据库操作 + +## 问题 + +1. Go 如何连接 MySQL 数据库? +2. database/sql 的使用方法是什么? +3. 如何使用 ORM(如 GORM)? +4. 如何处理数据库事务? +5. 如何处理 SQL 注入? + +--- + +## 标准答案 + +### 1. 连接 MySQL + +#### **使用 database/sql** + +```go +import ( + "database/sql" + _ "github.com/go-sql-driver/mysql" +) + +func main() { + // 连接数据库 + dsn := "user:password@tcp(127.0.0.1:3306)/dbname?parseTime=true" + db, err := sql.Open("mysql", dsn) + if err != nil { + panic(err) + } + defer db.Close() + + // 测试连接 + err = db.Ping() + if err != nil { + panic(err) + } + + fmt.Println("Connected!") +} +``` + +--- + +### 2. database/sql 使用 + +#### **查询** + +```go +func queryUser(db *sql.DB, id int) (string, error) { + var name string + err := db.QueryRow("SELECT name FROM users WHERE id = ?", id).Scan(&name) + if err != nil { + return "", err + } + return name, nil +} +``` + +--- + +#### **查询多行** + +```go +func queryUsers(db *sql.DB) ([]string, error) { + rows, err := db.Query("SELECT name FROM users WHERE age > ?", 18) + if err != nil { + return nil, err + } + defer rows.Close() + + var names []string + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return nil, err + } + names = append(names, name) + } + + return names, nil +} +``` + +--- + +### 3. 使用 GORM + +#### **安装** + +```bash +go get -u gorm.io/gorm +go get -u gorm.io/driver/mysql +``` + +--- + +#### **基本使用** + +```go +import ( + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +type User struct { + ID uint `gorm:"primaryKey"` + Name string `gorm:"column:name"` + Age int `gorm:"column:age"` +} + +func main() { + // 连接数据库 + dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + if err != nil { + panic(err) + } + defer db.Close() + + // 自动迁移 + db.AutoMigrate(&User{}) + + // 创建 + user := User{Name: "Alice", Age: 25} + db.Create(&user) + + // 查询 + var users []User + db.Where("age > ?", 18).Find(&users) +} +``` + +--- + +### 4. 事务处理 + +#### **database/sql 事务** + +```go +func transfer(db *sql.DB, from, to int, amount int) error { + tx, err := db.Begin() + if err != nil { + return err + } + + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + + // 1. 扣款 + _, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from) + if err != nil { + return err + } + + // 2. 存款 + _, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to) + if err != nil { + return err + } + + return nil +} +``` + +--- + +#### **GORM 事务** + +```go +func transfer(db *gorm.DB, from, to int, amount int) error { + return db.Transaction(func(tx *gorm.DB) error { + // 扣款 + if err := tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from).Error; err != nil { + return err + } + + // 存款 + if err := tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to).Error; err != nil { + return err + } + + return nil + }) +} +``` + +--- + +### 5. SQL 注入防护 + +#### ✅ 使用参数化查询 + +```go +// ✅ 安全(参数化查询) +db.Query("SELECT * FROM users WHERE id = ?", id) + +// ❌ 不安全(字符串拼接) +db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = %d", id)) +``` + +--- + +### 6. 阿里 P7 加分项 + +**深度理解**: +- 理解 database/sql 的连接池机制 +- 理解 GORM 的底层实现(基于 database/sql) +- 理解数据库事务的隔离级别 + +**实战经验**: +- 有处理数据库连接池的经验 +- 有优化数据库查询的经验(索引、批量操作) +- 有处理分布式事务的经验 + +**性能优化**: +- 理解如何优化数据库查询(预编译语句、批量操作) +- 理解如何优化连接池配置 +- 理解如何处理慢查询 diff --git a/questions/13-Golang语言/go-engineering.md b/questions/13-Golang语言/go-engineering.md new file mode 100644 index 0000000..36ff8f4 --- /dev/null +++ b/questions/13-Golang语言/go-engineering.md @@ -0,0 +1,187 @@ +# Golang 项目结构和工程化 + +## 问题 + +1. Go 的标准项目结构是怎样的? +2. Go Module 是如何管理的? +3. Go 的依赖管理有哪些最佳实践? +4. 如何构建和发布 Go 应用? +5. Go 的 CI/CD 最佳实践是什么? + +--- + +## 标准答案 + +### 1. 标准项目结构 + +#### **Standard Go Project Layout** + +``` +myapp/ +├── cmd/ # 主程序入口 +│ └── myapp/ +│ └── main.go +├── internal/ # 私有应用代码(不可导入) +│ ├── app/ +│ ├── model/ +│ └── service/ +├── pkg/ # 公共库(可外部导入) +│ └── util/ +├── api/ # API 协议定义 +│ └── proto/ +├── web/ # Web 静态资源 +├── configs/ # 配置文件 +├── scripts/ # 构建、安装脚本 +├── test/ # 额外测试数据 +├── docs/ # 文档 +├── tools/ # 工具 +├── go.mod +├── go.sum +├── Makefile +└── README.md +``` + +--- + +### 2. Go Module + +#### **初始化 Module** + +```bash +go mod init github.com/user/myapp +``` + +--- + +#### **go.mod 文件** + +```go +module github.com/user/myapp + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/spf13/viper v1.18.2 +) +``` + +--- + +### 3. 依赖管理 + +#### **添加依赖** + +```bash +go get github.com/gin-gonic/gin@latest +go get github.com/spf13/viper@v1.18.2 +``` + +--- + +#### **升级依赖** + +```bash +# 升级所有依赖 +go get -u ./... + +# 升级特定依赖 +go get -u github.com/gin-gonic/gin +``` + +--- + +#### **清理未使用的依赖** + +```bash +go mod tidy +``` + +--- + +### 4. 构建和发布 + +#### **构建应用** + +```bash +# 构建 +go build -o bin/myapp ./cmd/myapp + +# 交叉编译 +GOOS=linux GOARCH=amd64 go build -o bin/myapp-linux ./cmd/myapp +``` + +--- + +#### **Docker 多阶段构建** + +```dockerfile +# 构建 +FROM golang:1.21-alpine AS builder +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -o myapp ./cmd/myapp + +# 运行 +FROM alpine:latest +WORKDIR /root/ +COPY --from=builder /app/myapp . +CMD ["./myapp"] +``` + +--- + +### 5. CI/CD 最佳实践 + +#### **GitHub Actions** + +```yaml +name: CI + +on: + push: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Install dependencies + run: go mod download + + - name: Run tests + run: go test -v -race -coverprofile=coverage.out ./... + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + files: ./coverage.out +``` + +--- + +### 6. 阿里 P7 加分项 + +**深度理解**: +- 理解 Go 的模块管理(Go Modules) +- 理解 Go 的依赖管理(go.mod、go.sum) +- 理解 Go 的构建和发布流程 + +**实战经验**: +- 有管理大型 Go 项目结构的经验 +- 有设计 Go Module 依赖的经验 +- 有构建和发布 Go 应用的经验 + +**工程化**: +- 理解如何组织 Go 项目结构 +- 理解如何进行依赖管理 +- 理解如何设置 CI/CD diff --git a/questions/13-Golang语言/go-goroutine.md b/questions/13-Golang语言/go-goroutine.md new file mode 100644 index 0000000..0673f76 --- /dev/null +++ b/questions/13-Golang语言/go-goroutine.md @@ -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、批处理) diff --git a/questions/13-Golang语言/go-http-web.md b/questions/13-Golang语言/go-http-web.md new file mode 100644 index 0000000..ac1ac08 --- /dev/null +++ b/questions/13-Golang语言/go-http-web.md @@ -0,0 +1,191 @@ +# Golang HTTP 和 Web 开发 + +## 问题 + +1. Go 的 http.Client 如何使用? +2. 如何实现 HTTP 服务器? +3. context 在 HTTP 中的作用是什么? +4. 如何处理 HTTP 超时? +5. 如何实现中间件(Middleware)模式? + +--- + +## 标准答案 + +### 1. HTTP Client + +#### **基本使用** + +```go +import ( + "fmt" + "io" + "net/http" + "time" +) + +func main() { + // GET 请求 + resp, err := http.Get("https://api.github.com") + if err != nil { + panic(err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + fmt.Println(string(body)) +} +``` + +--- + +#### **自定义 Client** + +```go +func main() { + // 自定义 Transport(超时、代理等) + tr := &http.Transport{ + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + client := &http.Client{ + Transport: tr, + Timeout: 5 * time.Second, + } + + resp, err := client.Get("https://api.github.com") + // ... +} +``` + +--- + +### 2. HTTP Server + +#### **基本服务器** + +```go +import ( + "fmt" + "net/http" +) + +func handler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:]) +} + +func main() { + http.HandleFunc("/", handler) + http.ListenAndServe(":8080", nil) +} +``` + +--- + +#### **Server 高级用法** + +```go +func main() { + server := &http.Server{ + Addr: ":8080", + Handler: myHandler, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 120 * time.Second, + } + + server.ListenAndServe() +} +``` + +--- + +### 3. Context 在 HTTP 中的作用 + +#### **超时控制** + +```go +func handler(w http.ResponseWriter, r *http.Request) { + ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) + defer cancel() + + // 使用 context + req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.github.com", nil) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + if ctx.Err() == context.DeadlineExceeded { + http.Error(w, "Timeout", http.StatusRequestTimeout) + return + } + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + // ... +} +``` + +--- + +### 4. 中间件模式 + +```go +type Middleware func(http.Handler) http.Handler + +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + next.ServeHTTP(w, r) + fmt.Printf("%s %s %v\n", r.Method, r.URL, time.Since(start)) + }) +} + +func authMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + if token == "" { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + next.ServeHTTP(w, r) + }) +} + +func main() { + var mux http.ServeMux + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello, World!") + }) + + // 链式中间件 + handler := loggingMiddleware(authMiddleware(&mux)) + + http.ListenAndServe(":8080", handler) +} +``` + +--- + +### 5. 阿里 P7 加分项 + +**深度理解**: +- 理解 Go 的 http.Client 和 http.Server 实现 +- 理解 HTTP/2 支持 +- 理解 context 的传播机制 + +**实战经验**: +- 有开发高性能 HTTP 服务器的经验 +- 有处理 HTTP 超时的经验 +- 有实现中间件框架的经验 + +**性能优化**: +- 理解如何优化 HTTP 客户端性能(连接池、并发) +- 理解如何优化 HTTP 服务器性能(Handler 调度) +- 理解如何处理慢客户端 diff --git a/questions/13-Golang语言/go-memory-model.md b/questions/13-Golang语言/go-memory-model.md new file mode 100644 index 0000000..9d6ca03 --- /dev/null +++ b/questions/13-Golang语言/go-memory-model.md @@ -0,0 +1,260 @@ +# Golang 内存模型和垃圾回收 + +## 问题 + +1. Go 的内存分配策略是什么? +2. Go 的垃圾回收(GC)是如何工作的? +3. 什么是逃逸分析? +4. Go 的内存泄漏常见场景有哪些? +5. sync.Pool 的作用是什么? +6. 如何优化 Go 程序的内存使用? + +--- + +## 标准答案 + +### 1. 内存分配策略 + +#### **堆和栈** + +**栈(Stack)**: +- 存储局部变量 +- 自动分配和释放 +- 速度快 + +```go +func foo() int { + x := 1 // 分配在栈上 + return x +} // x 自动释放 +``` + +--- + +**堆(Heap)**: +- 存储动态分配的数据 +- 需要 GC 回收 + +```go +func foo() *int { + x := 1 + return &x // x 逃逸到堆上 +} +``` + +--- + +#### **逃逸分析** + +**定义**:编译器分析变量的生命周期,决定分配在栈还是堆。 + +**示例**: +```go +// ❌ 逃逸到堆 +func foo() *int { + x := 1 + return &x // x 的引用被返回,必须分配在堆上 +} + +// ✅ 不逃逸,分配在栈上 +func bar() int { + x := 1 + return x // x 的值被返回,可以分配在栈上 +} +``` + +--- + +### 2. Go 的垃圾回收 + +#### **GC 算法:三色标记法 + 并发标记清理** + +**三色标记**: +- **白色**:未访问 +- **灰色**:已访问,但引用的对象未全部访问 +- **黑色**:已访问,引用的对象也全部访问 + +**流程**: +``` +1. 根节点标记为灰色 +2. 遍历引用对象,标记为灰色 +3. 递归标记,直到所有可达对象为黑色 +4. 清扫白色对象(垃圾) +``` + +--- + +#### **GC 触发条件** + +```go +// 环境变量 +// GOGC:触发 GC 的堆增长率(默认 100) +// GOMEMLIMIT:触发 GC 的堆内存上限 + +// 示例 +GOGC=100 // 堆增长 100% 时触发 GC +GOMEMLIMIT=8192 // 堆上限 8 GB +``` + +--- + +### 3. 常见内存泄漏场景 + +#### **场景 1:Goroutine 泄漏** + +```go +// ❌ 泄漏 +func leak() { + for i := 0; i < 100; i++ { + go func() { + time.Sleep(time.Hour) // 永不退出 + }() + } +} +``` + +**解决**: +```go +func noLeak() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + for i := 0; i < 100; i++ { + go func(ctx context.Context) { + select { + case <-ctx.Done(): + return + case <-time.After(time.Hour): + } + }(ctx) + } +} +``` + +--- + +#### **场景 2:切片引用** + +```go +// ❌ 泄漏 +func leak() { + for i := 0; i < 1000; i++ { + s := make([]int, 1024) + _ = append(s, i) + // s 被全局变量引用,无法 GC + } +} +``` + +--- + +### 4. sync.Pool + +#### **作用** + +**对象池**:复用对象,减少 GC 压力 + +--- + +#### **示例** + +```go +var pool = sync.Pool{ + New: func() interface{} { + return make([]byte, 1024) + }, +} + +func process() { + // 从池中获取 + buf := pool.Get().([]byte) + + // 使用 + // ... + + // 归还到池中 + pool.Put(buf) +} +``` + +--- + +#### **适用场景** + +- 频繁创建的对象([]byte、Buffer) +- 临时对象(减少 GC) +- 对象创建成本高 + +--- + +### 5. 内存优化技巧 + +#### **1. 预分配切片容量** + +```go +// ❌ 多次扩容 +s := []int{} +for i := 0; i < 1000; i++ { + s = append(s, i) // 多次扩容和拷贝 +} + +// ✅ 预分配 +s := make([]int, 0, 1000) +for i := 0; i < 1000; i++ { + s = append(s, i) // 无扩容 +} +``` + +--- + +#### **2. 使用 strings.Builder** + +```go +// ❌ 字符串拼接(内存拷贝) +s := "" +for i := 0; i < 1000; i++ { + s += fmt.Sprintf("%d", i) // 每次都创建新字符串 +} + +// ✅ strings.Builder +var b strings.Builder +for i := 0; i < 1000; i++ { + b.WriteString(strconv.Itoa(i)) +} +s := b.String() // 只分配一次 +``` + +--- + +#### **3. 避免不必要的指针** + +```go +// ❌ 使用指针(增加 GC 扫描时间) +type User struct { + Name *string +} + +// ✅ 使用值类型 +type User struct { + Name string +} +``` + +--- + +### 6. 阿里 P7 加分项 + +**深度理解**: +- 理解 Go 的内存分配策略(栈、堆、逃逸分析) +- 理解 Go GC 的三色标记法 +- 理解 GC 的触发条件和调优参数 + +**实战经验**: +- 有使用 pprof 进行内存分析的经验 +- 有优化内存使用的经验(减少 GC、复用对象) +- 有处理内存泄漏的经验 + +**性能优化**: +- 理解如何减少内存分配(sync.Pool、预分配) +- 理解如何减少 GC 压力(避免指针、复用对象) +- 理解如何使用 pprof 进行性能分析 diff --git a/questions/13-Golang语言/go-performance.md b/questions/13-Golang语言/go-performance.md new file mode 100644 index 0000000..6975e67 --- /dev/null +++ b/questions/13-Golang语言/go-performance.md @@ -0,0 +1,286 @@ +# Golang 性能优化 + +## 问题 + +1. Go 的性能分析工具有哪些? +2. pprof 如何使用? +3. 如何减少内存分配? +4. 如何优化 CPU 性能? +5. 如何进行并发性能优化? +6. Go 的性能优化最佳实践是什么? + +--- + +## 标准答案 + +### 1. 性能分析工具 + +#### **pprof(CPU 和内存分析)** + +```bash +# CPU 性能分析 +go test -cpuprofile=cpu.prof -bench . + +# 内存分析 +go test -memprofile=mem.prof -bench . + +# 可视化 +go tool pprof -http=:8080 cpu.prof +``` + +--- + +#### **trace** + +```go +import ( + "os" + "runtime/trace" +) + +func main() { + f, _ := os.Create("trace.out") + defer f.Close() + + trace.Start(f) + defer trace.Stop() + + // 程序逻辑 +} +``` + +--- + +### 2. pprof 使用 + +#### **CPU 分析** + +```bash +# 生成 CPU profile +go test -cpuprofile=cpu.prof -bench=. + +# 查看 +go tool pprof cpu.prof + +# 列出 top 函数 +go tool pprof -top cpu.prof + +# 可视化 +go tool pprof -http=:8080 cpu.prof +``` + +--- + +#### **内存分析** + +```bash +# 生成 heap profile +go test -memprofile=heap.prof -bench=. + +# 查看 +go tool pprof heap.prof + +# 查看内存分配 +go tool pprof -alloc_space heap.prof +``` + +--- + +### 3. 减少内存分配 + +#### **1. 使用 sync.Pool** + +```go +var bufPool = sync.Pool{ + New: func() interface{} { + return make([]byte, 1024) + }, +} + +func process(data []byte) { + buf := bufPool.Get().([]byte) + defer bufPool.Put(buf) + + copy(buf, data) + // ... +} +``` + +--- + +#### **2. 预分配容量** + +```go +// ❌ 多次扩容 +var s []int +for i := 0; i < 1000; i++ { + s = append(s, i) +} + +// ✅ 预分配 +s := make([]int, 0, 1000) +for i := 0; i < 1000; i++ { + s = append(s, i) +} +``` + +--- + +### 4. CPU 性能优化 + +#### **1. 减少系统调用** + +```go +// ❌ 多次系统调用 +var b bytes.Buffer +for i := 0; i < 1000; i++ { + b.WriteString(strconv.Itoa(i)) +} + +// ✅ 批量写入 +var b bytes.Buffer +for i := 0; i < 1000; i++ { + b.WriteString(strconv.Itoa(i)) +} +``` + +--- + +#### **2. 避免不必要的拷贝** + +```go +// ❌ 拷贝 +func process(data []byte) { + copy := make([]byte, len(data)) + copy(copy, data) + // ... +} + +// ✅ 使用切片 +func process(data []byte) { + sub := data[10:20] + // ... +} +``` + +--- + +### 5. 并发性能优化 + +#### **1. 使用 Worker Pool** + +```go +func worker(id int, jobs <-chan int, results chan<- int) { + for j := range jobs { + results <- j * j + } +} + +func main() { + jobs := make(chan int, 100) + results := make(chan int, 100) + + // 启动 4 个 worker + for i := 0; i < 4; i++ { + go worker(i, jobs, results) + } + + // 发送任务 + for i := 0; i < 100; i++ { + jobs <- i + } + close(jobs) + + // 收集结果 + for i := 0; i < 100; i++ { + <-results + } +} +``` + +--- + +#### **2. 减少 Lock 竞争** + +```go +// ❌ 全局锁 +var mu sync.Mutex + +func process(id int) { + mu.Lock() + defer mu.Unlock() + // ... +} + +// ✅ 分片锁 +var mu [16]sync.Mutex + +func process(id int) { + idx := id % 16 + mu[idx].Lock() + defer mu[idx].Unlock() + // ... +} +``` + +--- + +### 6. 性能优化最佳实践 + +#### **1. 使用 pprof 分析瓶颈** + +```bash +# CPU 分析 +go tool pprof -http=:8080 cpu.prof + +# 内存分析 +go tool pprof -http=:8080 heap.prof + +# 火焰图 +go tool pprof -http=:8080 -http=:8080 -pdf cpu.prof > cpu.pdf +``` + +--- + +#### **2. 优化热点代码** + +```go +// 使用 pprof 找到热点函数 +// go tool pprof -list functionName cpu.prof + +// 优化前 +func slow() { + for i := 0; i < 1000000; i++ { + s := fmt.Sprintf("%d", i) // 慢 + _ = s + } +} + +// 优化后 +func fast() { + var b strings.Builder + for i := 0; i < 1000000; i++ { + b.WriteString(strconv.Itoa(i)) + } + _ = b.String() +} +``` + +--- + +### 7. 阿里 P7 加分项 + +**深度理解**: +- 理解 pprof 的数据采集原理 +- 理解 Go 的性能瓶颈(内存、CPU、Goroutine) +- 理解 Go 的 GC 对性能的影响 + +**实战经验**: +- 有使用 pprof 优化程序性能的经验 +- 有优化并发程序的经验(减少锁、使用 Worker Pool) +- 有优化内存使用的经验(sync.Pool、预分配) + +**性能优化**: +- 理解如何进行性能分析和瓶颈定位 +- 理解如何优化 CPU 密集型代码 +- 理解如何优化 I/O 密集型代码 diff --git a/questions/13-Golang语言/go-reflect-unsafe.md b/questions/13-Golang语言/go-reflect-unsafe.md new file mode 100644 index 0000000..a2c055e --- /dev/null +++ b/questions/13-Golang语言/go-reflect-unsafe.md @@ -0,0 +1,214 @@ +# Golang 反射和 unsafe + +## 问题 + +1. Go 的反射(reflect)有什么用? +2. 反射的性能如何?如何优化? +3. unsafe 包的作用是什么? +4. 什么场景下需要使用 unsafe? +5. 反射和 unsafe 的最佳实践是什么? + +--- + +## 标准答案 + +### 1. 反射基础 + +#### **基本用法** + +```go +import ( + "fmt" + "reflect" +) + +type User struct { + Name string `json:"name"` + Age int `json:"age"` +} + +func main() { + u := User{Name: "Alice", Age: 25} + + // 获取类型 + t := reflect.TypeOf(u) + fmt.Println(t.Name()) // User + fmt.Println(t.Kind()) // struct + + // 获取值 + v := reflect.ValueOf(u) + fmt.Println(v.CanAddr()) // true(可寻址) + fmt.Println(v.CanSet()) // true(可设置) + + // 读取字段 + field := v.FieldByName("Name") + fmt.Println(field.String()) // Alice + + // 设置字段 + field.SetString("Bob") +} +``` + +--- + +### 2. 反射的性能 + +#### **性能对比** + +```go +func directAccess(u *User) string { + return u.Name // 快 +} + +func reflectAccess(u interface{}) string { + v := reflect.ValueOf(u) + field := v.FieldByName("Name") + return field.String() // 慢(比直接访问慢 100 倍) +} +``` + +--- + +#### **优化反射** + +**1. 缓存反射结果**: + +```go +var ( + userType reflect.Type + nameField reflect.StructField +) + +func init() { + userType = reflect.TypeOf(User{}) + nameField, _ = userType.FieldByName("Name") +} + +func fastReflectAccess(u interface{}) string { + v := reflect.ValueOf(u) + return v.FieldByName("Name").String() // 已缓存类型 +} +``` + +**2. 使用代码生成**: + +```bash +# 使用 stringer +//go:generate go run golang.org/x/tools/cmd/stringer -type=User +``` + +--- + +### 3. unsafe 包 + +#### **作用** + +**绕过 Go 的类型系统**: +- 直接操作内存 +- 提高性能 +- 但不安全(可能崩溃) + +--- + +#### **示例** + +```go +import ( + "fmt" + "unsafe" +) + +func main() { + // 1. 指针运算 + nums := []int{1, 2, 3} + fmt.Println(len(nums)) // 3 + + // 2. 直接访问内存 + ptr := unsafe.Pointer(&nums[0]) + * (*int)(ptr) = 999 + fmt.Println(nums) // [999 2 3] + + // 3. uintptr 和 unsafe.Pointer 转换 + var a int = 42 + ptr := unsafe.Pointer(&a) + uptr := uintptr(ptr) + ptr2 := unsafe.Pointer(uptr) + fmt.Println(*(*int)(ptr2)) // 42 +} +``` + +--- + +### 4. unsafe 使用场景 + +#### **场景 1:高性能序列化** + +```go +import ( + "encoding/binary" + "unsafe" +) + +func WriteInt(w io.Writer, v int) error { + return binary.Write(w, binary.LittleEndian, v) +} + +func WriteIntUnsafe(w io.Writer, v int) error { + // 直接写入内存(更快) + return binary.Write(w, binary.LittleEndian, v) +} +``` + +--- + +#### **场景 2:字符串转字节(零拷贝)** + +```go +import "unsafe" + +func StringToBytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer( + &struct { + string *string + slice []byte + }{&s, nil}, + )) +} +``` + +--- + +### 5. 最佳实践 + +#### **反射**: + +1. **避免在热点路径使用**:反射慢 +2. **缓存反射结果**:减少开销 +3. **考虑代码生成**:更快的性能 + +--- + +#### **unsafe**: + +1. **谨慎使用**:可能导致崩溃 +2. **写注释**:解释为什么使用 unsafe +3. **添加测试**:确保正确性 + +--- + +### 6. 阿里 P7 加分项 + +**深度理解**: +- 理解反射的实现原理(类型、值、方法) +- 理解 unsafe 的内存模型(指针、内存对齐) +- 理解 Go 的类型系统(安全性、性能) + +**实战经验**: +- 有使用反射优化代码的经验 +- 有使用 unsafe 优化性能的经验 +- 有代码生成的经验 + +**性能优化**: +- 理解何时使用反射(配置加载、序列化) +- 理解何时使用 unsafe(高性能、零拷贝) +- 理解如何权衡安全性和性能 diff --git a/questions/13-Golang语言/go-testing.md b/questions/13-Golang语言/go-testing.md new file mode 100644 index 0000000..8088803 --- /dev/null +++ b/questions/13-Golang语言/go-testing.md @@ -0,0 +1,408 @@ +# Golang 错误处理和测试 + +## 问题 + +1. Go 的错误处理机制是什么?为什么不用 try-catch? +2. panic 和 recover 的使用场景是什么? +3. errors.Wrap 和 errors.Is 的作用是什么? +4. Go 的单元测试如何写?有哪些最佳实践? +5. table-driven test 是什么? +6. Go 的基准测试(Benchmark)如何写? + +--- + +## 标准答案 + +### 1. Go 的错误处理 + +#### **error 接口** + +```go +type error interface { + Error() string +} +``` + +**特点**: +- 错误是值,不是异常 +- 必须显式处理 +- 避免嵌套过深 + +--- + +#### **创建错误** + +```go +import "errors" + +// 1. 简单错误 +err1 := errors.New("something went wrong") + +// 2. fmt.Errorf +err2 := fmt.Errorf("invalid user: %s", "alice") + +// 3. 错误包装(Go 1.13+) +if err != nil { + return fmt.Errorf("query failed: %w", err) +} +``` + +--- + +#### **处理错误** + +```go +func readFile(path string) ([]byte, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read file failed: %w", err) + } + return data, nil +} +``` + +--- + +### 2. panic 和 recover + +#### **panic** + +**用途**:不可恢复的错误(程序无法继续运行) + +```go +// panic 示例 +func doPanic() { + panic("something terrible happened") +} + +func main() { + doPanic() + fmt.Println("this will not print") +} +``` + +--- + +#### **recover** + +**用途**:捕获 panic,防止程序崩溃 + +```go +func safeExecute() { + defer func() { + if err := recover(); err != nil { + fmt.Println("recovered from:", err) + } + }() + + panic("oh no!") +} + +func main() { + safeExecute() + fmt.Println("program continues") +} +``` + +--- + +#### **最佳实践** + +1. **避免使用 panic**:除非是真正不可恢复的错误 +2. **recover 只在 defer 中使用**:其他地方无效 +3. **库代码不应 panic**:应该返回 error + +--- + +### 3. 错误包装和判断 + +#### **errors.Wrap** + +```go +import ( + "errors" + "fmt" +) + +func queryDB() error { + return errors.New("connection failed") +} + +func service() error { + err := queryDB() + if err != nil { + return fmt.Errorf("service failed: %w", err) // 包装错误 + } + return nil +} + +func main() { + err := service() + if err != nil { + // 检查错误链 + if errors.Is(err, errors.New("connection failed")) { + fmt.Println("is connection error") + } + + // 获取最底层的错误 + var connErr error = errors.New("connection failed") + if errors.Is(err, connErr) { + fmt.Println("connection error occurred") + } + } +} +``` + +--- + +#### **自定义错误类型** + +```go +import "errors" + +type ValidationError struct { + Field string + Message string +} + +func (e *ValidationError) Error() string { + return fmt.Sprintf("%s: %s", e.Field, e.Message) +} + +func validate(name string) error { + if name == "" { + return &ValidationError{Field: "name", Message: "required"} + } + return nil +} + +func main() { + err := validate("") + var ve *ValidationError + if errors.As(err, &ve) { + fmt.Println("validation error:", ve.Field, ve.Message) + } +} +``` + +--- + +### 4. 单元测试 + +#### **基本测试** + +```go +package mypackage + +import "testing" + +func Add(a, b int) int { + return a + b +} + +func TestAdd(t *testing.T) { + result := Add(1, 2) + if result != 3 { + t.Errorf("Add(1, 2) = %d; want 3", result) + } +} +``` + +--- + +#### **Table-Driven Test(表格驱动测试)** + +```go +func TestAddTableDriven(t *testing.T) { + tests := []struct { + a, b int + want int + }{ + {1, 2, 3}, + {2, 3, 5}, + {10, -5, 5}, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("%d+%d", tt.a, tt.b), func(t *testing.T) { + if got := Add(tt.a, tt.b); got != tt.want { + t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want) + } + }) + } +} +``` + +--- + +#### **子测试(Subtests)** + +```go +func TestAll(t *testing.T) { + t.Run("Test1", func(t *testing.T) { + // Test1 + }) + + t.Run("Test2", func(t *testing.T) { + // Test2 + }) +} +``` + +--- + +### 5. 基准测试(Benchmark) + +#### **基本 Benchmark** + +```go +func BenchmarkAdd(b *testing.B) { + for i := 0; i < b.N; i++ { + Add(1, 2) + } +} +``` + +**运行**: +```bash +go test -bench=. -benchmem +``` + +--- + +#### **Benchmark 高级用法** + +```go +func BenchmarkAddParallel(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + Add(1, 2) + } + }) +} + +func BenchmarkLargeSlice(b *testing.B) { + b.ResetTimer() // 重置计时器 + data := make([]int, 1000000) + b.StartTimer() + + for i := 0; i < b.N; i++ { + _ = len(data) + } +} +``` + +--- + +### 6. Mock 和测试替身 + +#### **使用接口进行 Mock** + +```go +type Database interface { + Query(id int) (string, error) +} + +type Service struct { + db Database +} + +func (s *Service) GetUser(id int) (string, error) { + return s.db.Query(id) +} + +// Mock 实现 +type MockDatabase struct { + MockQuery func(id int) (string, error) +} + +func (m *MockDatabase) Query(id int) (string, error) { + return m.MockQuery(id) +} + +// 测试 +func TestService_GetUser(t *testing.T) { + mockDB := &MockDatabase{ + MockQuery: func(id int) (string, error) { + return "Alice", nil + }, + } + + service := &Service{db: mockDB} + name, err := service.GetUser(1) + + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if name != "Alice" { + t.Errorf("got %s; want Alice", name) + } +} +``` + +--- + +### 7. 测试最佳实践 + +#### **1. 测试覆盖率** + +```bash +# 查看覆盖率 +go test -cover + +# 生成覆盖率报告 +go test -coverprofile=coverage.out +go tool cover -html=coverage.out +``` + +--- + +#### **2. 测试隔离** + +```go +// 每个测试独立 +func Test1(t *testing.T) { + // setup + // test + // teardown +} + +func Test2(t *testing.T) { + // 不依赖 Test1 +} +``` + +--- + +#### **3. 使用 TestMain** + +```go +func TestMain(m *testing.M) { + // 全局 setup + fmt.Println("setup before all tests") + + m.Run() + + // 全局 teardown + fmt.Println("teardown after all tests") +} +``` + +--- + +### 8. 阿里 P7 加分项 + +**深度理解**: +- 理解 Go 错误处理的设计哲学(显式优于隐式) +- 理解 panic/recover 的实现机制 +- 理解 table-driven test 的优势 + +**实战经验**: +- 有编写高质量单元测试的经验(覆盖率 > 80%) +- 有使用 Mock 进行测试的经验 +- 有编写 Benchmark 进行性能优化的经验 + +**测试策略**: +- 理解如何设计可测试的代码(依赖注入、接口) +- 理解如何进行集成测试和端到端测试 +- 理解如何设计测试用例(边界条件、异常情况)