Go并发编程、依赖、测试 | 青训营笔记

147 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记

GO-学习笔记

Go并发编程

  • 在用户态实现资源调度

并发与并行

  • 并发,同一个CPU,切换执行
  • 并行,同时多个CPU上,一起执行,是实现并发的手段

运行机制

  • 协程,用户台,轻量级线程,栈MB级别,用户级线程

go中的并发运行单位

  • 线程,内核台,线程跑多个协程,栈KB级别

开启协程

  • 关键字go
go func(word string) {
    //执行完成 执行Done()
   defer wg.Done()
   queryCaiyunai(word)

}(word) //该word为参数

协程间通信

通信共享内存

  • go提倡通过通信共享内存,通过通道chan共享数据
  • 如果指定大小,为有缓冲通道;没有指定大小,为无缓冲通道,也是同步通道

如果达到缓冲大小,则会阻塞

ch := make(chan int, 10)
arr := []int{1, 2, 3, 4, 5}
for _, n := range arr {
    // 入通道
   ch <- n
}
// 出通道
a, b, c, d, e := <-ch, <-ch, <-ch, <-ch, <-ch
fmt.Println(a, b, c, d, e)

并发编程中的应用

  • 调用
go sum(arr[:len(arr)/2], ch)
go sum(arr[len(arr)/2:], ch)
x, y := <-ch, <-ch
fmt.Println(x, y, x+y)
  • sum函数
func sum(nums []int, ch chan int) {
   sum := 0
   for _, n := range nums {
      sum += n
   }
   ch <- sum
}

共享内存来通信

  • sync.Mutex
var (
   x    int64
   lock sync.Mutex
)

func main() {
   for i := 0; i < 2000; i++ {
      lock.Lock()
      x += 1
      lock.Unlock()
   }
}

WaitGroup

  • 维护了计时器
  • 创建 wg := &sync.WaitGroup{}
  • 计数+1 wg.Add(2)
  • 计数-1 wg.Done()

底层调用wg.Add(-1)

  • 计数不为0时阻塞 wg.Wait()

标准用法

wg := &sync.WaitGroup{}
wg.Add(2)
word := os.Args[1]
// 匿名函数
go func(word string) {
    //执行完成 执行Done()
   defer wg.Done()
   queryCaiyunai(word)

}(word) //该word为参数
go func(word string) {
   defer wg.Done()
   queryHuoshan(word)
}(word)
wg.Wait()

Errgroup

eg := errgroup.Group{}
eg.Go(func() error {
   err := queryCaiyunai(word)
   return err
})
eg.Go(func() error {
   err := queryHuoshan(word)
   return err
})
if err := eg.Wait(); err != nil {
   fmt.Println(err)
}

依赖管理

  • 不同环境/项目的依赖版本不同,且控制依赖库的版本
  • 同一个项目中的两个子项目依赖不同的版本时,选择最新的兼容版本

GOPATH

  • 打开cd $GOPATH
  • bin 项目编译的二进制文件
  • pkg 项目编译的中间产物
  • src 项目源码
  • go get下载最新版本的包到src
  • 弊端,无法实现package的多版本控制

Vendor

  • 在项目目录下增加vendor目录,所有依赖包以副本形式存放在vendor
  • vendorGOPATH下寻找
  • 弊端,同一个项目,依赖的包版本冲突,无法控制依赖的版本

Go Module

  • 通过go.mod管理依赖包
  • 通过go get/go mod指令工具管理依赖包
  • go get拉去某个包,类比cocoapods中的pod install
  1. go mod之后,进行依赖
  2. go install 安装bin文件,不会修改go.mod
  • go mod
  1. init初始化
  2. download下载模块
  3. tidy增加需要的依赖,删除不需要的依赖

依赖管理三要素

  • 类比maven
  • go.mod 配置文件,描述依赖
  • Proxy 中心仓库管理依赖库
  • go get/mod 本地工具

依赖分发

  • Proxy,保证依赖的稳定性
  • 配置GOPROXY,为url列表

依次查找"url1, url2, direct"direct为源站

测试

  • 测试文件_test.go结尾
  • 函数格式
func Testxxxx(t *testing.T) {
   //test...
}
  • 导入包import "testing"
  • TestMain进行初始化逻辑,执行test方法时执行
func TestMain(m *testing.M) {
   //...资源管理/初始化操作
   m.Run()
}
  • go test命令行工具,执行当前包下的所有test方法

测试的代码覆盖率,go test go_test.go go.go --cover

assert

  • 验证是否满足条件
s := queryName()
m := "output1"
assert.Equal(t, m, s)

image.png

mock机制

  • 保证测试的幂等和稳定

幂等,多次相同的操作结果一致

  • 替换函数,将真正要执行的函数,替换成模拟函数

真实情况下,函数的功能执行往往需要复杂的初始化过程,可以通过mock打桩,进行替换,用不同的函数返回值进行测试

  • 框架bou.ke/monkey
  • 使用,将ReadFirstLine函数的执行,替换成后面闭包的执行,只需要模拟真实的返回值即可

运行时,替换成对应的子函数调用

func TestProcessFirstLineWithMock(t *testing.T) {
   monkey.Patch(ReadFirstLine, func() string {
      return "line110"
   })
   // 删除补丁
   defer monkey.Unpatch(ReadFirstLine)
   line := ProcessFirstLine()
   assert.Equal(t, "line000", line)
}

基准测试

  • 测试单元性能和CPU损耗,多次以不同的数量级进行运行
  • Benchmark开头
func BenchmarkSelect(b *testing.B) {
   InitServerIndex()
   // 重设其实时间
   b.ResetTimer()
   // b.N 数量级,会不断变大
   for i := 0; i < b.N; i++ {
      Select()
   }
}

image.png

基准并发测试

func BenchmarkSelectParallel(b *testing.B) {
   InitServerIndex()
   b.ResetTimer()
   b.RunParallel(func(pb *testing.PB) {
      for pb.Next() {
         Select()
      }
   })
}