Golang上下文context和反射reflect

75 阅读4分钟

context

Context 也叫作上下文,程序单元则指的是 Goroutine。

每个 Goroutine 在执行之前,都要先知道程序当前的执行状态,通常将这些执行状态封装在一个 Context 变量中,传递给要执行的 Goroutine 中。

context可以用来在goroutine之间传递上下文信息,相同的context可以传递给运行在不同goroutine中的函数,上下文对于多个goroutine同时使用是安全的,context包定义了上下文类型,可以使用backgroundTODO创建一个上下文,在函数调用链之间传播context,也可以使用WithDeadlineWithTimeoutWithCancelWithValue 创建的修改副本替换它

创建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_idcontext,然后不断透传下去,打印日志时输出即可

  • 不建议使用context值传递关键参数,关键参数应该显示的声明出来,不应该隐式处理,context中最好是携带签名、trace_id这类值。
  • 因为携带value也是keyvalue的形式,为了避免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

withTimeoutwithDeadline作用是一样的,就是传递的时间参数不同而已,他们都会通过传入的时间来自动取消Context,这里要注意的是他们都会返回一个cancelFunc方法,通过调用这个方法可以达到提前进行取消,不过在使用的过程还是建议在自动取消后也调用cancelFunc去停止定时减少不必要的资源浪费。

withTimeoutWithDeadline不同在于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)
    }
}
​