1716 字
9 分钟
Go 高级特性
Go 高级特性
一、错误处理机制
1.1 defer机制
-
defer 的作用
- 用来指定在函数返回前执行的代码
- 主要用于资源释放(文件关闭、锁释放、数据库连接关闭)
- 配合recover捕获panic
-
defer 的执行顺序
- LIFO(后进先出)顺序执行
- 多个defer形成defer栈
- 函数返回时从栈顶开始执行
-
defer底层实现
- Go 1.12及之前:所有defer在堆上分配
- Go 1.13:引入栈上分配,提升性能
- Go 1.14:引入开放编码(open-coded),进一步优化
type _defer struct { siz int32 // 参数和返回值的内存大小 started bool // defer是否已经开始执行 heap bool // 是否分配在堆上 openDefer bool // 是否使用开放编码优化(Go 1.14+) sp uintptr // 栈指针,记录defer语句时的栈位置 pc uintptr // 程序计数器,defer函数的返回地址 fn *funcval // defer延迟调用的函数 _panic *_panic // 触发defer的panic结构体 link *_defer // 链接到下一个defer,形成链表}
- defer陷阱
// 陷阱1:循环中的deferfor i := 0; i < 1000; i++ {defer file.Close() // 错误:defer在函数返回时才执行}// 陷阱2:defer参数即时求值func a() {i := 0defer fmt.Println(i) // 输出0,不是1i++}// 陷阱3:defer与返回值func foo() (result int) {defer func() {result++ // 可以修改命名返回值}()return 0 // 最终返回1}
1.2 panic和recover
-
panic机制
- Go的运行时异常机制
- 未被recover捕获会导致程序崩溃
- 输出堆栈信息
-
panic触发方式
- 主动调用:
panic("error message")
- 运行时错误:
- 空指针解引用
- 数组/切片越界
- 类型断言失败
- 向已关闭的channel发送数据
- 并发map读写
- 主动调用:
-
panic执行流程
- 停止当前函数的正常执行
- 执行当前函数的所有defer(LIFO顺序)
- 返回调用者,重复步骤1-2
- 直到被recover捕获或程序终止
-
recover使用规则
- 必须在defer函数中直接调用
- 只能捕获当前goroutine的panic
- recover后从defer之后继续执行
- 返回panic的值
func example() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered:", r) } }() panic("something went wrong") fmt.Println("This won't execute")}
二、并发通信
2.1 channel基础
-
channel的作用
- goroutine之间通信
- 同步goroutine执行
- 实现CSP并发模型
-
channel底层结构
type hchan struct { qcount uint // 当前缓冲区中的元素数量 dataqsiz uint // 缓冲区的容量 buf unsafe.Pointer // 指向环形缓冲区 elemsize uint16 // 元素大小 closed uint32 // 关闭标志 elemtype *_type // 元素类型 sendx uint // 发送索引 recvx uint // 接收索引 recvq waitq // 等待接收的goroutine队列 sendq waitq // 等待发送的goroutine队列 lock mutex // 互斥锁}
- 发送和接收流程
- 发送:加锁 → 检查是否关闭 → 尝试直接发送给接收者 → 写入缓冲区 → 加入sendq等待
- 接收:加锁 → 尝试从缓冲区读取 → 尝试从发送者接收 → 加入recvq等待
2.2 channel分类
-
按方向分类
var ch1 chan int // 双向channelvar ch2 <-chan int // 只读channelvar ch3 chan<- int // 只写channel// 双向可以转换为单向ch1 = make(chan int)ch2 = ch1 // 双向转只读ch3 = ch1 // 双向转只写 -
按缓冲分类
- 无缓冲channel:同步通信,发送和接收必须同时准备好
// 死锁示例func main() {ch := make(chan int)ch <- 42 // 阻塞,等待接收者<-ch // 永远执行不到}// 正确用法func main() {ch := make(chan int)go func() {ch <- 42}()fmt.Println(<-ch)}- 有缓冲channel:异步通信,缓冲区满时发送阻塞,空时接收阻塞
2.3 channel操作
-
关闭channel的影响
- 向已关闭的channel发送:panic
- 从已关闭的channel接收:返回零值和false
- 重复关闭channel:panic
- 关闭nil channel:panic
-
判断channel是否关闭
// 方法1:双返回值value, ok := <-chif !ok {fmt.Println("Channel closed")}// 方法2:for range(推荐)for value := range ch {fmt.Println(value)}// channel关闭后自动退出// 方法3:select with defaultselect {case v, ok := <-ch:if !ok {fmt.Println("Channel closed")}default:fmt.Println("No data available")}
三、上下文管理
3.1 context接口
type Context interface { // Done返回一个channel,当context被取消或超时时关闭 Done() <-chan struct{}
// Err返回context结束的原因 // context.Canceled:被取消 // context.DeadlineExceeded:超时 Err() error
// Deadline返回context的截止时间 // ok=false表示没有设置deadline Deadline() (deadline time.Time, ok bool)
// Value返回context中的键值对 Value(key interface{}) interface{}}
3.2 context类型
- context.Background():根context,通常用于main函数
- context.TODO():不确定使用哪个context时的占位符
- context.WithCancel():可取消的context
- context.WithDeadline():带截止时间的context
- context.WithTimeout():带超时的context
- context.WithValue():携带键值对的context
3.3 context最佳实践
// 1. context应该作为第一个参数func DoSomething(ctx context.Context, arg string) error
// 2. 不要存储contexttype Server struct { // ctx context.Context // 错误}
// 3. 传递请求范围的值,不传递可选参数ctx = context.WithValue(ctx, "requestID", "12345") // 正确// ctx = context.WithValue(ctx, "dbConfig", config) // 错误
// 4. 使用context控制goroutine生命周期func worker(ctx context.Context) { for { select { case <-ctx.Done(): return // 优雅退出 default: // 执行工作 } }}
四、控制流
4.1 switch语句
-
基本特性
- 不需要break,自动终止
- case可以是多个值:
case 1, 2, 3:
- case表达式运行时求值
- switch后无表达式相当于
switch true
-
fallthrough
- 强制执行下一个case(不判断条件)
- 不能用在最后一个case
- 不能用在类型switch中
-
类型switch
func typeSwitch(x interface{}) {switch v := x.(type) {case int:fmt.Printf("int: %d\n", v)case string:fmt.Printf("string: %s\n", v)case []int:fmt.Printf("[]int: %v\n", v)default:fmt.Printf("unknown type: %T\n", v)}}
五、面向对象特性
5.1 Go的面向对象
-
Go没有类和继承
- 使用结构体代替类
- 使用组合代替继承
- 使用接口实现多态
-
方法接收者
// 值接收者func (p Point) Distance() float64 {// 不能修改p}// 指针接收者func (p *Point) Move(dx, dy float64) {// 可以修改p} -
组合实现”继承”
type Animal struct {Name string}func (a Animal) Speak() {fmt.Println("...")}type Dog struct {Animal // 嵌入Breed string}// Dog"继承"了Animal的Speak方法// 可以"重写"(遮蔽)func (d Dog) Speak() {fmt.Println("Woof!")} -
接口实现多态
type Speaker interface {Speak()}// Dog和Cat都实现Speaker接口func MakeSound(s Speaker) {s.Speak() // 多态调用}
六、反射机制
6.1 反射基础
- reflect.Type:类型信息
- reflect.Value:值信息
- reflect.TypeOf():获取类型
- reflect.ValueOf():获取值
6.2 反射三定律
- 反射可以从接口值得到反射对象
- 反射可以从反射对象得到接口值
- 要修改反射对象,值必须可设置(CanSet)
6.3 反射应用
// 获取结构体字段信息func inspectStruct(s interface{}) { t := reflect.TypeOf(s) v := reflect.ValueOf(s)
for i := 0; i < t.NumField(); i++ { field := t.Field(i) value := v.Field(i) fmt.Printf("%s: %v (tag: %s)\n", field.Name, value, field.Tag) }}
// 动态调用方法func callMethod(obj interface{}, method string) { v := reflect.ValueOf(obj) m := v.MethodByName(method) if m.IsValid() { m.Call(nil) }}