[ Go语言进阶与依赖管理 |青训营笔记 ]

75 阅读2分钟

Go语言进阶与依赖管理

语言进阶-并发编程

  • 并发:

    • 多线程运行运行在cpu的一个核
    • 多线程运行运行在cpu的多个核
  • Goroutine

    • 协程,协程运行在cpu用户态,可以看做轻量级的线程,栈mb级别
    • 线程,运行在cpu内核态,一个线程可以运行多个协程,栈kb级别
  • go关键字启动一个 goroutine,

    • var i=1
      go func(j int){
          fmt.Println(j)
      }(i)
      time.sleep(time.second) //确保主线程在协程结束后退出
      
    • 携程之间通信DSP communiting sequential processes

    • 通过通信共享内存而不是共享内存实现goroutine间通信

    • Channel通道

      • make (chan int) 创建无缓冲通道

      • make (chan int,2) 创造有缓冲通道

        • int是通信数据类型,2是指通道容量大小

        • 简单的例子: 生产-消费模型, 协程输出0-9数字平方, dest代缓冲
        • func ValSquare(){
              src := make(chan int)
              dest := make(chan int, 3)
              go func(){
                  defer close(src)
                  for i :=0;i < 10; i++{
                      src <- i
                  }
              }()
              go func{
                  defer close(dest)
                  for i := range src{
                      dest <- i*i
                  }
              }()
              for i: range dest{
                  println(i)
              }
          }
          
    • 并发安全 lock sync.mutex

      • lock和unlock
      • var(
            x int64
            lock sync.Mutex
        )
        
        func addWithLock(){
            for i: 0; i< 2000; i++{
                lock.lock()
                x+=1
                lock.unlock()
            }
        }
        
        
        func addWithoutLock(){
            for i: 0; i< 2000; i++{
                x+=1
            }
        }
        
        func Add(){
            x=0
            for i:=0; i<5; i++{
                go addWithLock()
            }
            time.Sleep(time.Second)
            println("WithLock:"x)
            x=0
            for i:=0; i<5; i++{
                go addWithoutLock()
            }
            time.Sleep(time.Second)
            println("WithoutLock:"x)
        }
        
    • WaitGroup保证并发任务的同步

    •   内部维护了一个计数器, 用来记录协程的数目

      • Add(), 计数器增加数值
      • Done(), 计数器数值减一
      • Wait(), 堵塞协程, 只有当技术器数值为0, 函数才能返回
    • func MantGoWait(){
          var wg sync.WaitGrou
          wg.Add(5) //记录5个协程
          for i :=0; i < 5; i++{
              go func(j int){
                  defer wg.Done()
                  println(j)
              }(i)
          }
          wg.wait()
      }
      

依赖管理

  • 依赖管理的演进路线

    • GOPATH

      •     环境变量, 指向Go的工作目录

      • 目录结构

      • 项目代码直接依赖于src目录下的代码

      • 存在问题: 无法实现package多版本控制

    • Go okgWendor

      • 在GOPATH基础上, 在每个项目目录下, 增加了vendor目录, 先在项目目录寻找依赖,再到公共依赖库寻找依赖
      • 存在问题: 无法展示依赖的层级结构
    • GO Module

      • 依赖管理三要素

        • 配置文件 go.mod
        • 中心仓库 Proxy
        • 本地工具 go get/mod
      • Go.mod文件

      • 版本配置-version

        • 语义版本 ${MAJOR}.${MINOR}.${PATCH}
        • 抑郁commit 伪版本
      • 关键字

        • indirect 标识非直接依赖

        • Incompatible 标识没有go.mod,并且版本2+的依赖

        •       , 会在版本号号=后加这个关键字, 表示可能会有不兼容问题

        • 依赖版本选择时, 会选择最低的兼容版本

        • 依赖分发

          • 直接从远程库下载的问题
          • Proxy
          • 寻找依赖顺序: proxy1->proxy2->Direct
        • 工具

          • go get
          • go mod

测试

  • 一些真实发生的事故

  • 测试分类

    • 回归测试: 实际服务体验
    • 集成测试: 对对外提供的接口矩进行自动化测试
    • 单元测试: 对函数,模块进行测试
  • 测试规则

    • 测试文件命名: _test.go结尾
    • 测试函数名: func TestXxx(*testing.T)
    • 初始化逻辑放到TestMain
    • func TestMain(m *testing.T){
          //测试前, 初始化, 数据准备
          code :=m.Run()
          //测试后, 释放资源等
          os.exit(0)
      }
      
  • 简单例子

    • func HelloTom() string{
          return "Jerry";
      }
      
    •     func TestHelloTom(t *testing.T){
              output :=HelloTom()
              expectOutput := "Tom"
              if output !=exceptOutput{
                  t.Errorf("Excepted %S do not match actual %S ",exceptOutput, output)
              } 
          }
      
    • go test hello_test.go hello.go
  • 单元测试-assert

    • 导入开源的assert包 github.com/stretchr/testify/assert

    • assert.Equal(t,expectOutput, output)
      
    • 评估测试-覆盖率

      • 执行程序, 显示覆盖率

      • go test  hello_test.go hello.go --cover 
        
      • cover计算: 运行到的代码行数/总代码行数

      • 一般覆盖率:50-60, 较高为80+

      • 测试要求

        • 测试分支相互独立, 覆盖全面
        • 单元测试粒度足够小, 函数职责单一
  • 单元测试-依赖

    • 要求

      • 幂等性: 运行多次,结果是一致的
      • 稳定性: 任何时间,任何函数中运行依赖, 不依赖网络或者本地文件等环境,这就是mock测试
  • 单元测试-Mock

  • 单元测试文件管理

  • 基准测试: 测试程序的运行性能和cpu的消耗程度

    • 负债均衡例子
    • package main
      
      import (
          "math/rand"
          "testing"
      )
      
      var ServerIndex [10]int
      
      func InitServerIndex() {
          for i := 0; i < 10; i++ {
             ServerIndex[i] = i + 100
          }
      }
      
      func Select() int {
          return ServerIndex[rand.Intn(10)]
      }
      //基准测试函数Benchmaek开头, 入参是b * testing.B
      //当函数执行时间不超过1秒时, 这里N的值会自动递增,1, 2, 5, 10, 20, 50... 
      func BenchmarkSelect(b *testing.B) {
          //ResetTimer重置计时器, 避免初始时间的干扰
          b.ResetTimer()
          for i := 0; i < b.N; i++ {
             Select()
          }
      }
      
      //协程并发测试, 发现在多协程下程序有效率问题, 因为rand内使用了全局锁
      func BenchmarkSelectPapallel(b *testing.B) {
          InitServerIndex()
          b.ResetTimer()
          b.RunParallel(func(pb *testing.PB) {
             for pb.Next() {
                Select()
             }
          })
      }
      //可以使用字节开源的randAPI, 可以解决这个问题
      //func Select() int {
      //    return ServerIndex[fastrand.Intn(10)]
      //}