• GoLang核心知识点


    目录

    1. 系统中断信号注册

    2. 通道接收多个返回值

    3. go context

    4. reflect

    5. json字符串对象转换


    1. 系统中断信号注册

    1. interrupt := make(chan os.Signal) // 可以控制强制终止的信号
    2. // 如果系统有中断信号,发送给r.interrupt
    3. signal.Notify(interrupt, os.Interrupt)
    4. // Ctrl+C 退出
    5. sig := make(chan os.Signal, 1)
    6. signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
    7. fmt.Printf("quit (%v)\n", <-sig)

    2. 通道接收多个返回值

    通道接收的多参返回,如果可以接收的话,第一参数是接收的值,第二个表示通道是否关闭,false表示通道关闭,true表示通道正常。

    1. res := make(chan io.Closer, size)
    2. r, ok := <- res

    3. go context

    1. func main() {
    2. ctx, cancel := context.WithCancel(context.Background())
    3. go func(ctx context.Context) {
    4. for {
    5. select {
    6. case <-ctx.Done():
    7. fmt.Println("监控退出,停止了...")
    8. return
    9. default:
    10. fmt.Println("goroutine监控中...")
    11. time.Sleep(2 * time.Second)
    12. }
    13. }
    14. }(ctx)
    15. time.Sleep(10 * time.Second)
    16. fmt.Println("可以了,通知监控停止")
    17. cancel()
    18. //为了检测监控过是否停止,如果没有监控输出,就表示停止了
    19. time.Sleep(5 * time.Second)
    20. }

    context.Background() 返回一个空的Context,这个空的Context一般用于整个Context树的根节点。然后我们使用context.WithCancel(parent)函数,创建一个可取消的子Context,然后当作参数传给goroutine使用,这样就可以使用这个子Context跟踪这个goroutine。在goroutine中,使用select调用<-ctx.Done()判断是否要结束,如果接受到值的话,就可以返回结束goroutine了;如果接收不到,就会继续进行监控。

    那么是如何发送结束指令的呢?这就是示例中的cancel函数啦,它是我们调用context.WithCancel(parent)函数生成子Context的时候返回的,第二个返回值就是这个取消函数,它是CancelFunc类型的。我们调用它就可以发出取消指令,然后我们的监控goroutine就会收到信号,就会返回结束。

    使用Context控制一个goroutine的例子如上,非常简单,下面我们看看控制多个goroutine的例子,其实也比较简单。

    1. func main() {
    2. ctx, cancel := context.WithCancel(context.Background())
    3. go watch(ctx,"【监控1】")
    4. go watch(ctx,"【监控2】")
    5. go watch(ctx,"【监控3】")
    6. time.Sleep(10 * time.Second)
    7. fmt.Println("可以了,通知监控停止")
    8. cancel()
    9. //为了检测监控过是否停止,如果没有监控输出,就表示停止了
    10. time.Sleep(5 * time.Second)
    11. }
    12. func watch(ctx context.Context, name string) {
    13. for {
    14. select {
    15. case <-ctx.Done():
    16. fmt.Println(name,"监控退出,停止了...")
    17. return
    18. default:
    19. fmt.Println(name,"goroutine监控中...")
    20. time.Sleep(2 * time.Second)
    21. }
    22. }
    23. }
    • 不要把Context放在结构体中,要以参数的方式传递
    • 以Context作为参数的函数方法,应该把Context作为第一个参数,放在第一位。
    • 给一个函数方法传递Context的时候,不要传递nil,如果不知道传递什么,就使用context.TODO
    • Context的Value相关方法应该传递必须的数据,不要什么数据都使用这个传递
    • Context是线程安全的,可以放心的在多个goroutine中传递

    4. reflect

    1. func main() {
    2. u:= User{"张三",20}
    3. t:=reflect.TypeOf(u)
    4. v := reflect.ValueOf(u)
    5. fmt.Println(t, v)
    6. // 一种简单的打印变量类型和变量值的方法
    7. fmt.Printf("%T, %v", u, u)
    8. }
    9. type User struct{
    10. Name string
    11. Age int
    12. }

    通过反射,我们可以获取一个结构体类型的字段,也可以获取一个类型的导出方法,这样我们就可以在运行时了解一个类型的结构,这是一个非常强大的功能。

    1. for i:=0;i<t.NumField();i++ {
    2. fmt.Println(t.Field(i).Name)
    3. }
    4. for i:=0;i<t.NumMethod() ;i++ {
    5. fmt.Println(t.Method(i).Name)
    6. }

    假如我们想在运行中动态的修改某个字段的值有什么办法呢?一种就是我们常规的有提供的方法或者导出的字段可以供我们修改,还有一种是使用反射,这里主要介绍反射。

    1. func main() {
    2. x:=2
    3. v:=reflect.ValueOf(&x)
    4. v.Elem().SetInt(100)
    5. fmt.Println(x)
    6. }

    因为reflect.ValueOf函数返回的是一份值的拷贝,所以前提是我们是传入要修改变量的地址。 其次需要我们调用Elem方法找到这个指针指向的值。 最后我们就可以使用SetInt方法修改值了。

    以上有几个重点,才可以保证值可以被修改,Value为我们提供了CanSet方法可以帮助我们判断是否可以修改该对象。

    结构体的方法我们不光可以正常的调用,还可以使用反射进行调用。要想反射调用,我们先要获取到需要调用的方法,然后进行传参调用,如下示例:

    1. func main() {
    2. u:=User{"张三",20}
    3. v:=reflect.ValueOf(u)
    4. mPrint:=v.MethodByName("Print")
    5. args:=[]reflect.Value{reflect.ValueOf("前缀")}
    6. fmt.Println(mPrint.Call(args))
    7. }
    8. type User struct{
    9. Name string
    10. Age int
    11. }
    12. func (u User) Print(prfix string){
    13. fmt.Printf("%s:Name is %s,Age is %d",prfix,u.Name,u.Age)
    14. }

    MethodByName方法可以让我们根据一个方法名获取一个方法对象,然后我们构建好该方法需要的参数,最后调用Call就达到了动态调用方法的目的。

    获取到的方法我们可以使用IsValid 来判断是否可用(存在)。

    这里的参数是一个Value类型的数组,所以需要的参数,我们必须要通过ValueOf函数进行转换。

    5. json字符串对象转换

    1. func main() {
    2. var u User
    3. h:=`{"name":"张三","age":15}`
    4. err:=json.Unmarshal([]byte(h),&u)
    5. if err!=nil{
    6. fmt.Println(err)
    7. }else {
    8. fmt.Println(u)
    9. }
    10. }
    11. type User struct{
    12. Name string `name`
    13. Age int `age`
    14. }

    可以通过反射获取字段的tag

    1. func main() {
    2. var u User
    3. t:=reflect.TypeOf(u)
    4. for i:=0;i.NumField();i++{
    5. sf:=t.Field(i)
    6. fmt.Println(sf.Tag)
    7. }
    8. }

    很多时候我们的一个Struct不止具有一个功能,比如我们需要JSON的互转、还需要BSON以及ORM解析的互转,所以一个字段可能对应多个不同的Tag,以便满足不同的功能场景。Go Struct 为我们提供了键值对的Tag,来满足我们以上的需求。

    1. func main() {
    2. var u User
    3. t:=reflect.TypeOf(u)
    4. for i:=0;i.NumField();i++{
    5. sf:=t.Field(i)
    6. fmt.Println(sf.Tag.Get("json"))
    7. }
    8. }
    9. type User struct{
    10. Name string `json:"name"`
    11. Age int `json:"age"`
    12. }

    也可以设置多个key

    1. func main() {
    2. var u User
    3. t:=reflect.TypeOf(u)
    4. for i:=0;i.NumField();i++{
    5. sf:=t.Field(i)
    6. fmt.Println(sf.Tag.Get("json"),",",sf.Tag.Get("bson"))
    7. }
    8. }
    9. type User struct{
    10. Name string `json:"name" bson:"b_name"`
    11. Age int `json:"age" bson:"b_age"`
    12. }

    多个Key使用空格进行分开,然后使用Get方法获取不同Key的值。

    Struct Tag可以提供字符串到Struct的映射能力,以便我们作转换,除此之外,还可以作为字段的元数据的配置,提供我们需要的配置,比如生成Swagger文档等。

  • 相关阅读:
    leetcode刷题:二叉树10(完全二叉树的节点个数)
    SpringBoot默认开启AOP,采用Cglib代理方式?(Spring AOP快速入门)
    基于Hadoop+Java+MySQL的歌曲推荐管理系统设计与实现
    普瑞PS8742 Switch V0.9
    源码分析:OTA 更新
    标准I/O和系统I/O的本质区别
    我想知道linux机器上某个文本有没有重复行
    MS5192T/MS5193T——低噪声、低功耗、16/24 位∑-ΔADC
    [watevrCTF-2019]Cookie Store
    c库函数:strrchr使用demo案例
  • 原文地址:https://blog.csdn.net/FENGQIYUNRAN/article/details/137881220