context
Context 也叫作上下文,程序单元则指的是 Goroutine。
每个 Goroutine 在执行之前,都要先知道程序当前的执行状态,通常将这些执行状态封装在一个 Context 变量中,传递给要执行的 Goroutine 中。
context可以用来在goroutine之间传递上下文信息,相同的context可以传递给运行在不同goroutine中的函数,上下文对于多个goroutine同时使用是安全的,context包定义了上下文类型,可以使用background、TODO创建一个上下文,在函数调用链之间传播context,也可以使用WithDeadline、WithTimeout、WithCancel 或 WithValue 创建的修改副本替换它
创建context
context包主要提供了两种方式创建context:
context.Backgroud()context.TODO()
这两个函数其实只是互为别名,没有差别,官方给的定义是:
context.Background是上下文的默认值,所有其他的上下文都应该从它衍生(Derived)出来。context.TODO应该只在不确定应该使用哪种上下文时使用;所以在大多数情况下,我们都使用
context.Background作为起始的上下文向下传递
上面的两种方式是创建根context,不具备任何功能,具体实践还是要依靠context包提供的With系列函数来进行派生:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
基于一个父Context可以随意衍生,其实这就是一个Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个,每个子节点都依赖于其父节点
WithValue
我们日常在业务开发中都希望能有一个trace_id能串联所有的日志,这就需要我们打印日志时能够获取到这个trace_id,在python中我们可以用gevent.local来传递,在java中我们可以用ThreadLocal来传递,在Go语言中我们就可以使用Context来传递,通过使用WithValue来创建一个携带trace_id的context,然后不断透传下去,打印日志时输出即可
- 不建议使用
context值传递关键参数,关键参数应该显示的声明出来,不应该隐式处理,context中最好是携带签名、trace_id这类值。 - 因为携带
value也是key、value的形式,为了避免context因多个包同时使用context而带来冲突,key建议采用内置类型。
package main
import (
"context"
"fmt"
)
func main() {
type favContextKey string // 定义一个key类型
// f:一个从上下文中根据key取value的函数
f := func(ctx context.Context, k favContextKey) {
if v := ctx.Value(k); v != nil {
fmt.Println("found value:", v)
return
}
fmt.Println("key not found:", k)
}
k := favContextKey("language")
// 创建一个携带key为k,value为"Go"的上下文
ctx := context.WithValue(context.Background(), k, "Go")
f(ctx, k)
f(ctx, favContextKey("color"))
}
withTimeout\withDeadline
withTimeout和withDeadline作用是一样的,就是传递的时间参数不同而已,他们都会通过传入的时间来自动取消Context,这里要注意的是他们都会返回一个cancelFunc方法,通过调用这个方法可以达到提前进行取消,不过在使用的过程还是建议在自动取消后也调用cancelFunc去停止定时减少不必要的资源浪费。
withTimeout、WithDeadline不同在于WithTimeout将持续时间作为参数输入而不是时间对象,这两个方法使用哪个都是一样的,看业务场景和个人习惯了,因为本质withTimout内部也是调用的WithDeadline。
- 达到超时时间终止接下来的执行
func main() {
HttpHandler()
}
func NewContextWithTimeout() (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), 3*time.Second)
}
func HttpHandler() {
ctx, cancel := NewContextWithTimeout()
defer cancel()
deal(ctx)
}
func deal(ctx context.Context) {
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
select {
case <-ctx.Done():
fmt.Println(ctx.Err())
return
default:
fmt.Printf("deal time is %d\n", i)
}
}
}
//output
deal time is 0
deal time is 1
context deadline exceeded
withCancel
日常业务开发中我们往往为了完成一个复杂的需求会开多个gouroutine去做一些事情,这就导致我们会在一次请求中开了多个goroutine确无法控制他们,这时我们就可以使用withCancel来衍生一个context传递到不同的goroutine中,当我想让这些goroutine停止运行,就可以调用cancel来进行取消。
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
go gen1(ctx)
time.Sleep(3 * time.Second)
cancel()
time.Sleep(2 * time.Second)
}
func gen1(ctx context.Context) {
for range time.Tick(time.Second) {
select {
case <-ctx.Done():
fmt.Println("done")
return
default:
fmt.Println("gen1")
}
}
}
反射
反射是指在程序运行期对程序本身进行访问和修改的能力
reflect包封装了反射相关的方法
- 获取类型信息:reflect.TypeOf,是静态的
- 获取值信息:reflect.ValueOf,是动态的
变量反射
func main() {
var a int = 1
//获取类型信息
t := reflect.TypeOf(a)
fmt.Println(t)
k := t.Kind()
fmt.Println(k)
//获取值信息
v := reflect.ValueOf(a)
fmt.Println(v)
//修改值信息
v1 := reflect.ValueOf(&a)
v1.Elem().SetInt(2)
fmt.Println(a)
}
结构体反射
https://www.topgoer.com/%E5%B8%B8%E7%94%A8%E6%A0%87%E5%87%86%E5%BA%93/%E5%8F%8D%E5%B0%84.html
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func (p Person) Show() {
fmt.Println(p.Name, p.Age)
}
func main() {
p := Person{"tom", 18}
t := reflect.TypeOf(p)
fmt.Println("type: ", t)
fmt.Println("类名: ", t.Name())
//获取值
v := reflect.ValueOf(p)
fmt.Println(v)
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
//获取字段信息
fmt.Printf("%s:%v\t", f.Name, f.Type)
//获取字段值
val := v.Field(i).Interface()
fmt.Println("val: ", val)
}
//方法信息
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Println(m.Name, m.Type)
}
}