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:
453
questions/13-Golang语言/go-basic-syntax.md
Normal file
453
questions/13-Golang语言/go-basic-syntax.md
Normal file
@@ -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 的扩容(预分配容量)
|
||||
- 理解如何减少锁竞争(分片锁、无锁编程)
|
||||
240
questions/13-Golang语言/go-concurrent-advanced.md
Normal file
240
questions/13-Golang语言/go-concurrent-advanced.md
Normal file
@@ -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)
|
||||
- 有实现无锁数据结构的经验
|
||||
|
||||
**并发编程**:
|
||||
- 理解如何避免数据竞争
|
||||
- 理解如何使用原子操作
|
||||
- 理解如何实现高性能并发程序
|
||||
225
questions/13-Golang语言/go-database.md
Normal file
225
questions/13-Golang语言/go-database.md
Normal file
@@ -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)
|
||||
- 理解数据库事务的隔离级别
|
||||
|
||||
**实战经验**:
|
||||
- 有处理数据库连接池的经验
|
||||
- 有优化数据库查询的经验(索引、批量操作)
|
||||
- 有处理分布式事务的经验
|
||||
|
||||
**性能优化**:
|
||||
- 理解如何优化数据库查询(预编译语句、批量操作)
|
||||
- 理解如何优化连接池配置
|
||||
- 理解如何处理慢查询
|
||||
187
questions/13-Golang语言/go-engineering.md
Normal file
187
questions/13-Golang语言/go-engineering.md
Normal file
@@ -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
|
||||
390
questions/13-Golang语言/go-goroutine.md
Normal file
390
questions/13-Golang语言/go-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、批处理)
|
||||
191
questions/13-Golang语言/go-http-web.md
Normal file
191
questions/13-Golang语言/go-http-web.md
Normal file
@@ -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 调度)
|
||||
- 理解如何处理慢客户端
|
||||
260
questions/13-Golang语言/go-memory-model.md
Normal file
260
questions/13-Golang语言/go-memory-model.md
Normal file
@@ -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 进行性能分析
|
||||
286
questions/13-Golang语言/go-performance.md
Normal file
286
questions/13-Golang语言/go-performance.md
Normal file
@@ -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 密集型代码
|
||||
214
questions/13-Golang语言/go-reflect-unsafe.md
Normal file
214
questions/13-Golang语言/go-reflect-unsafe.md
Normal file
@@ -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(高性能、零拷贝)
|
||||
- 理解如何权衡安全性和性能
|
||||
408
questions/13-Golang语言/go-testing.md
Normal file
408
questions/13-Golang语言/go-testing.md
Normal file
@@ -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 进行性能优化的经验
|
||||
|
||||
**测试策略**:
|
||||
- 理解如何设计可测试的代码(依赖注入、接口)
|
||||
- 理解如何进行集成测试和端到端测试
|
||||
- 理解如何设计测试用例(边界条件、异常情况)
|
||||
Reference in New Issue
Block a user