Go 基础解析
涵盖基础语法、并发、内存管理、接口设计、标准库与工具、性能优化以及实战场景
一、基础语法与类型
1. Go 与 C/Java 的主要区别
- 内存管理:Go 有垃圾回收(GC),不需要手动管理内存。
- 并发模型:Go 内置 goroutine 和 channel,而 C/Java 需要线程库或 Executor。
- 面向对象:Go 不支持继承,用组合 + 接口实现多态。
- 错误处理:Go 通过返回值 error 处理,而不是异常。
- 编译与部署:Go 生成静态二进制,部署简单;Java 需要 JVM。
2. Go 的数据类型
基础类型
布尔类型:bool
数值类型:
整型:int8, int16, int32, int64, uint8, uint16, uint32, uint64, int, uint, uintptr
浮点型:float32, float64
复数型:complex64, complex128
字符类型:
byte:uint8 的别名
rune:int32 的别名,表示一个 Unicode 码点
字符串类型:string
复合类型
数组类型:[n]T,长度固定的元素序列
切片类型:[]T,动态长度的元素序列
结构体类型:struct,字段集合,支持方法和接口
指针类型:*T,指向类型 T 的指针
函数类型:func,函数签名
接口类型:interface,定义方法集的类型
映射类型:map[K]V,键值对集合
通道类型:chan T,用于 goroutine 之间通信的管道
3. new 与 make 区别
关键点 | new | make |
---|---|---|
用途 | 分配内存 | 初始化 slice、map、chan |
返回值 | 指针 | 对象本身 |
例子 | p := new(int) // *int | s := make([]int,5) // []int |
4. defer 执行时机
- 在函数返回前执行,遵循 后进先出(LIFO)
- 常用于释放资源、解锁、打印日志
func f() {defer fmt.Println("first")defer fmt.Println("second")fmt.Println("function body")
}
// 输出:
// function body
// second
// first
5. 指针使用限制
- Go 不允许指针算术运算
- 避免野指针和内存越界
- 只能通过 new/make 或取地址 &var 获取指针
二、控制流与函数
1. 可变参数函数
func sum(nums ...int) int {total := 0for _, n := range nums {total += n}return total
}
2. 闭包(closure)
- 函数可以引用外部变量并返回函数,保持状态
func adder() func(int) int {sum := 0return func(x int) int {sum += xreturn sum}
}
3. panic、recover 与 error
- panic:运行时错误,触发堆栈展开
- recover:捕获 panic,防止程序崩溃
- error:函数返回错误,不影响堆栈
func safeDiv(a,b int) {defer func() {if r := recover(); r != nil {fmt.Println("Recovered from panic:", r)}}()fmt.Println(a/b) // b=0 会 panic
}
4. 方法与函数区别
- 方法绑定在 receiver 上
- 值接收者 vs 指针接收者影响是否能修改对象状态
type Point struct { x, y int }
func (p Point) Move(dx, dy int) { p.x += dx; p.y += dy } // 值接收
func (p *Point) MovePtr(dx, dy int) { p.x += dx; p.y += dy } // 指针接收
三、并发与调度
1. Goroutine
- 轻量级线程,使用
go func() {}
创建
2. runtime.Gosched()
- 让出当前 goroutine 的 CPU 时间片
- 不保证其他 goroutine 立即执行
- 不保证严格交替输出
go func() { for i:=0;i<10;i+=2{ fmt.Println(i); runtime.Gosched() } }()
go func() { for i:=1;i<10;i+=2{ fmt.Println(i); runtime.Gosched() } }()
3. Channel
- 无缓冲 channel(同步)
- 有缓冲 channel(异步,满时阻塞)
- select 多路复用
ch := make(chan int, 2)
ch <- 1
ch <- 2
select {
case v := <- ch:fmt.Println(v)
default:fmt.Println("no value")
}
4. sync.Mutex 与 sync.RWMutex
- Mutex:互斥锁
- RWMutex:读写锁,多个读不阻塞,写独占
5. WaitGroup
var wg sync.WaitGroup
wg.Add(1)
go func() {defer wg.Done()// do something
}()
wg.Wait()
6. Goroutine 泄漏排查
- channel 阻塞未关闭
- 无限循环或 select 默认分支未退出
- 使用 pprof/goroutine dump 检查堆栈
四、内存管理
1. 栈 vs 堆
- 栈:局部变量,函数返回自动释放
- 堆:逃逸分析判断需跨函数使用的变量分配到堆,GC 回收
2. slice 扩容原理
- slice 底层结构:ptr, len, cap
- append 超出 cap,会分配新数组并复制原数据
s := make([]int,0,2)
s = append(s,1,2,3) // cap 扩容
3. map 底层实现
- 哈希表 + 链表/红黑树冲突处理
- 随机 hash 减少碰撞
4. 指针逃逸分析
- 如果变量被返回或闭包引用,会分配到堆
go build -gcflags '-m'
可查看逃逸信息
五、接口与面向对象
1. 接口
- 定义行为,不定义实现
- 空接口
interface{}
可接收任意类型 - 类型断言/类型开关提取具体类型
var i interface{} = "hello"
s := i.(string)
switch v := i.(type) {
case string:fmt.Println("string:", v)
}
2. 多态实现
- 通过接口实现
type Shape interface { Area() float64 }
type Circle struct { r float64 }
func (c Circle) Area() float64 { return 3.14*c.r*c.r }
func PrintArea(s Shape) { fmt.Println(s.Area()) }
六、标准库与工具
1. Go module 与 GOPATH
- Module 是推荐方式,支持版本管理
- GOPATH 已逐渐弱化
2. context.Context
- 用于控制 goroutine 生命周期、超时、取消和传递请求范围值
3. net/http 与 fasthttp
- fasthttp 性能更高,但 API 不兼容 http
4. reflect 包
- 用于运行时类型信息
- 使用过度可能影响性能
5. log 包
- 简单日志打印
- 支持自定义输出、前缀和标志位
七、性能与优化
1. 性能分析工具
- pprof CPU / memory / goroutine profiling
- trace 分析并发阻塞、调度
2. 常见优化点
- 减少对象频繁分配
- 减少锁竞争
- 使用池(sync.Pool)复用对象
- 控制 goroutine 数量,避免泄漏
八、实战类场景题
1. 限流器(Token Bucket)
type TokenBucket struct {tokens intcapacity intmu sync.Mutex
}
func (tb *TokenBucket) Allow() bool {tb.mu.Lock()defer tb.mu.Unlock()if tb.tokens > 0 {tb.tokens--return true}return false
}
2. 生产者-消费者
ch := make(chan int, 10)
go func() {for i:=0;i<10;i++ { ch <- i }close(ch)
}()
go func() {for v := range ch { fmt.Println(v) }
}()
3. 协程池示例
tasks := make(chan func(), 100)
for i:=0;i<5;i++ {go func() {for task := range tasks { task() }}()
}
tasks <- func() { fmt.Println("task") }
4. 奇偶交替输出
odd := make(chan struct{})
even := make(chan struct{})
go func() {for i:=1;i<=10;i+=2 {<- oddfmt.Println(i)even <- struct{}{}}
}()
go func() {for i:=2;i<=10;i+=2 {<- evenfmt.Println(i)odd <- struct{}{}}
}()
odd <- struct{}{} // 启动奇数