并发&测试&依赖管理 | 青训营笔记

30 阅读2分钟

这是我参与「第五届青训营 」笔记创作活动的第2天

只不过是字节给我的任务罢了

Go并发

Golang使用Goroutine实现轻量级线程(协程),运行在用户态,栈KB级别

使用Goroutine

使用go关键字创建一个协程,只需要在调用函数语句之前加上go关键字即可

 go funcName

主协程结束,所有其它协程一起被终止

 package main
 ​
 import (
     "fmt"
     "time"
 )
 ​
 func main() {
     go spinner(100 * time.Millisecond)
     const n = 45
     fibN := fib(n) // slow
     fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
     for {
         // 如果有这个死循环,goroutine spinner()还会继续运行,主线程结束后,sipnner才会结束
     }
 }
 ​
 func spinner(delay time.Duration) {
     for {
         for _, r := range `-|/` {
             fmt.Printf("\r%c", r)
             time.Sleep(delay)
         }
     }
 }
 ​
 func fib(x int) int {
     if x < 2 {
         return x
     }
     return fib(x-1) + fib(x-2)
 }
 ​

协程间的通信

通道通过通信共享内存,而非通过共享内存实现通信(这种是临界区)

通道相当于将协程做了一个连接,实现协程之间的通信

Channel

goroutine是golang中在语言级别实现的轻量级线程,仅仅利用 go 就能立刻起一个新线程。多线程会引入线程之间的同步问题,在golang中可以使用channel作为同步的工具。 通过channel可以实现两个goroutine之间的通信。 创建一个channel, make(chan TYPE {, NUM}) , TYPE指的是channel中传输的数据类型,第二个参数是可选的,指的是channel的容量大小。 向channel传入数据, CHAN <- DATA , CHAN 指的是目的channel即收集数据的一方, DATA 则是要传的数据。 从channel读取数据, DATA := <-CHAN ,和向channel传入数据相反,在数据输送箭头的右侧的是channel,形象地展现了数据从‘隧道’流出到变量里。

使用make(chan TYPE {, NUM})函数创建通道(类型为chan),如果有提供了NUM(缓冲大小),那么这个通道则为有缓冲通道,反之是无缓冲通道(同步通道)

示例:

 func CalSquare() {
     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)      // 结果为依次输出 0 ~ 9 的相乘
     }
 }

并发安全

可以使用lock.Lock()对临界区加锁,使用lock.Unlock()解锁,释放临界区资源,使用方法同POSIX的pthread_mutex_lock/unlock

WaitGroup

使用WaitGroup控制主协程的等待时间

Add(delta int)实现计数器+delta

Done()实现计数器+1

Wait()阻塞到计数器为0

Go依赖库管理

GOPATH

 cd $GOPATH
 ls
 $: bin/ pkg/ src/ #项目编译的二进制文件;编译过程的中间产物,加速编译;源码

go get将最新版包下载到src目录下

GOPATH的弊端:无法实现package多版本控制

GoVender

在项目目录下增加vender文件,所有依赖包副本形式放在$ProjectRoot/vender

依赖寻址方式:vender=>GOPATH

GoVender的弊端:无法控制依赖版本,更新项目有可能出现依赖冲突,导致编译出错

Go Module

通过go.mod文件管理依赖包版本

通过go get/go mod指令工具管理依赖包

目的:定义版本规则和管理项目依赖关系

依赖管理三要素

  1. 配置文件,描述依赖:go.mod
  2. 中心仓库管理依赖库:Proxy
  3. 本地工具:go/get mod

依赖配置-go.mod

依赖标识:[Module Path][Version/Pseudo-version]

 module example/project/app # 依赖管理基本单元,可以设置为github目录
 go 1.16 # 原生库
 require ( # 单元依赖
     example/lib1 v1.0.2example/lib2 v1.0.0 // indirect
     example/lib3 v0.1.0-20190725025543-5a5fe074e612
     example/lib4 v0.0.0-20180306012644-bacd9c7ef1dd // indirect
     example/lib5/v3 v3.0.2
     example/lib6 v3.2.0+incompatible
 )

依赖配置-version

语义化版本

${MAJOR}.${MINOR}.${PATCH}

例:V1.3.0

基于commit伪版本

vX.0.0-yyyymmddhhmmss-abdcefgh1234

语义化版本-时间戳-哈希码

依赖配置-indirect

A依赖B,B依赖C,那么A直接依赖B,B直接依赖C,A间接依赖C

依赖配置-incompatible

主版本2+模块 会在模块路径后加/vN后缀

对于没有go.mod文件并且主版本2+的依赖,会+incompatible

依赖配置-依赖图

image-20230116164321688.png

选择最低的兼容版本v1.4

依赖分发-回源

image-20230116164558271.png 0. 无法保证构建稳定性 0. 无法保证依赖可用性 0. 增加第三方压力(代码托管平台的负载)

依赖分发-Proxy

使用Proxy保证依赖稳定性,直接从Proxy中拉取依赖

变量GOPROXY

GOPROXY="https://proxy1.cn, https://proxy2.cn, direct"

其中direct代表源站

首先从proxy1中拉取依赖,没有则拉取proxy2,还是没有则拉取源站direct

工具-go get

go get example.org/pkg @TAG

@TAG可以是

  1. @update 默认
  2. @none 删除依赖
  3. @v1.1.2 tag版本,语义版本
  4. @23dfcd5 特定的commit
  5. @master 分支的最新commit

工具-go mod

go mod TAG

TAG可以是

  1. init 初始化,创建go.mod文件
  2. download 下载模块到本地缓存
  3. tidy 增加需要的依赖,删除不需要的依赖

测试

  1. 回归测试
  2. 集成测试
  3. 单元测试

单元测试

输入->输出 \

                    >校对

         期望/

单元测试-规则

所有的测试文件以_test.go结尾

测试函数定义func TestXxx(t *testing.T)

初始化逻辑放在TestMain

 func TestMain(m *testing.M) {
     
     // 测试前,数据装载,配置初始化等前置工作
     
     code := m.Run() // 运行包内的所有单元测试
     
     // 测试后,释放资源
     
     os.Exit(code)
 }

单元测试-依赖

外部依赖=>稳定&&幂等=>Mock

assert

assert包中包含了Equal、NotEqual等等,我们可以调用asset包来简化代码

 import ("github.com/stretchr/testify/assert")

使用assert判断运行结果和期望值是否想等

 assert.Equal(t, expectOutput, output)

单元测试-Mock

github.com/bouk/monkey

  • 为一个函数打桩
  • 为一个方法打桩

使用函数A替换函数B,那么A就是打桩函数

 func Patch(target, replacement interface{}) *PatchGuard {
     t := refflect.ValueOf(target)
     r := reflect.ValueOf(replacement)
     patchValue(t, r)
     
     return &PatchGuard{t, r}
 }
 •
 func Unpatch(target interface{}) bool {
     return unpatchValue(reflect.ValueOf(target))
 }