【Go进阶知识点巩固 | 青训营笔记】

67 阅读2分钟

这是我参与【第五届青训营】伴学笔记创作活动的第七天,此篇概述一下自学的go的进阶知识点。

image.png

Go进阶知识点巩固

runtime包

常用函数:

runtime.GOROOT()  // 获取 goroot 目录
runtime.GOOS      // 获取操作系统
runtime.NumCPU()  // 获取逻辑 cpu 的数量
runtime.NumGOMAXPROCS(runtim.NumCPU())  // 设置go程序执行的最大cpu的数量
runtime.Gosched() // 让出时间片,让其他的go协程先执行
runtime.Goexit()  // 结束当前go协程,不影响defer函数的执行

临界资源

  1. 定义:指并发环境中多个进程/线程/协程共享的资源。
  2. 安全问题:借助于 sync 包下的锁操作(不建议,建议使用通道的方式)

sync 基础

  1. sync 基础应用

    // 同步等待组
    var wg sync.WaitGroup  // 创建同步等待组的对象
    wg.Add()  // 设置等待组要执行的子 goroutine 的数量
    wg.Wait() // 让主 goroutine 处于等待
    wg.Done() // 给 wg 等待组中的 counter 数值减 1
    ​
    func main() {
        wg.Add(2)
        go func() {
            defer wg.Done()
            fmt.Println("1")
        }()
        go func() {
            defer wg.Done()
            fmt.Println("2")
        }()
        fmt.Println("wait!")
        wg.Wait()
        fmt.Println("finish....")
    }
    
  2. 互斥锁

    只能有一个 goroutine 占用这个锁

    var mutex sync.Mutex  // 创建一个互斥锁
    mutex.Lock()    // 上锁
    mutex.Unlock()  // 解锁
    
  3. 读写锁

    var rwMutex *sync.RWMutex  // 创建读写锁
    rwMutex.RLock()   // 读操作上锁
    rwMutex.RUnlock() // 读操作解锁
    rwMutex.Lock()    // 写操作上锁
    rwMutex.Unlock()  // 写操作解锁 
    

channel 通道

  1. channel 非缓冲通道

    ch := make(chan, int) // 默认是阻塞的

    // 当互斥锁用
    func main() {
        ch := make(chan bool)
        go func() {
            for i:=0; i<10; i++ { fmt.Println(i) }
            ch <- true
        }()
        <- ch
        fmt.Println("over")
    }
    ​
    close(ch)          // 关闭通道
    data, ok := <- ch  // ok 若为 false则通道已关闭// 安全的传递所有的数据
    var ch chan int
    func main() {
        ch = make(chan int)
        go sendData()
        // 读取通道的数据
        /*for {
            time.Sleep(1 * time.Second)
            v, ok := <-ch
            if !ok {
                fmt.Println("data over!....", ok)
                break
            }
            fmt.Println("读取的数据是:", v, ok)
        }*/
        // 用 range 更好
        for data:= range ch {
            fmt.Println("读取的数据是:",data)
        }
        fmt.Println("main over...")
    }
    func sendData() {
        defer close(ch)
        for i := 0; i < 10; i++ {
            ch <- i
        }
    }
    
  2. channel 缓冲通道

    用法更灵活

    ch := make(chan int, capacity)  // 定义方法
    
  3. channel 定向通道

    即单向通道,只能读或写,上述都为双向通道。

    ch := make(chan <- int)  // 读通道
    ch := make(<- chan int)  // 写通道// 简单用法
    ch := make(chan int)
    f(ch)
    func f(ch1 chan <- int) {  
        // 可传入 ch, 但限制为只读
    }
    

Time 包中的通道相关函数

主要就是定时器,处理超时问题。

// NewTimer() 用法
timer := time.NewTimer(3*time.Second)  // 创建一个计时器
time2 := <- timer.C   // 结束时触发,返回当前时间
// After() 用法
timer := time.After(3*time.Second)
time2 := <- timer

go 的反射机制

  1. 反射初识:(reflect 包)

    func TypeOf( x interface{ } ) Type 函数,可传入任意类型值,判断类型

    func ValueOf(x interface{ } ) Value 函数

    var x float64 = 3.4
    // reflect.TypeOf(x)
    // reflect.ValueOf(x)
    v := reflect.ValueOf(x)
    fmt.Println("kind is float64: ", v.Kind()==reflect.Float64)
    fmt.Println("type: ", v.Type())
    fmt.Println("value: ", v.Float())
    

    对于反射,一般非必要不要使用。最大的原因是编译发现错误需要时间久,以及性能影响大,运行效率相对较慢。

  2. (静态类型+动态类型)

    var A interface{}  // 静态类型 interface{}
    A = 10             // 静态类型 interface{}, 动态类型 int 
    // A = "has"       // 可重新赋值为其他类型
    
  3. 反射的使用

    • 从 reflect.Value 获取接口 interface 信息 realValue := value.Interface().(type)

    • 获取字段

      type Person struct {
          Name string
          Age  int
          Sex  string
      }
      func main() {
          p := Person{"小林", 18, "男"}
          Getmessage(p)
      }
      // 获取 input 信息
      func Getmessage(input interface{}) {
          getType := reflect.TypeOf(input) // 先获取 input 的类型
          // fmt.Println(getType.Name())  // Person
          // fmt.Println(getType.Kind())  // struct
          getValue := reflect.ValueOf(input) // {小林 18 男}// 获取字段
          for i := 0; i < getType.NumField(); i++ {
              field := getType.Field(i)
              value := getValue.Field(i).Interface()
              fmt.Printf("字段名称:%s, 字段类型:%s, 字段数值:%v \n", field.Name, field.Type, value)
          }
      }
      
    • 通过 reflect.Value 设置实际变量值

      普通类型:

      var num float64 = 1.23
      pointer := reflect.ValueOf(&num)  // 指针
      newValue := pointer.Elem()  // 指针指向的元素
      newValue.CanSet()  // 是否可以修改
      newValue.SetFloat(3.14)  // 根据数的类型设置值
      

      结构体:

      type Person struct {
          Name string
          Age  int
          Sex  string
      }
      func main() {
          p := Person{"小林", 18, "男"}
          p1 := &p
          // 改变数值
          value := reflect.ValueOf(&p)
          if value.Kind() == reflect.Ptr {
              newValue := value.Elem()
              f1 := newValue.FieldByName("Name")  // 指定元素
              f1.SetString("xiaming")             // 设置值
          }
      }
      

      方法调用:

      func (p Person) PrintInfo() {
          fmt.Println("hahahahahah")
      }
      p := Person{"小林", 18, "男"}
      value := reflect.ValueOf(p)
      methodValue := value.MethodByName("PrintInfo")  // 根据函数名获取函数
      methodValue.Call(nil)  // 没有参数直接调用 , nil 或者 空切片也行
      

      函数调用:

      func f() {}
      value := reflect.ValueOf(f)
      value.Call(nil)  // 同理