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

98 阅读6分钟

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

go语言开发为何会如此之快?

并发编程

并发vs并行

  • 并发:多线程程序在一个核的cpu上运行(通过时间片的切换实现)
  • 并行:多线程程序在多个核的cpu上运行

image.png

Goroutine

  • 协程:用户态,轻量级线程,栈KB级别
  • 线程:内核态,线程跑多个协程,栈MB级别(ppt有点小错误)

image.png

协程Goroutine:为一个函数创建一个协程,只需要在该函数前加go关键字

  • 该程序通过time.Sleep(time.Second)实现暴力的阻塞 image.png

  • code
    此代码使用的是 Go语言等待组(sync.WaitGroup)实现并发 image.png

func hello(i int) {
   println("hello world : " + fmt.Sprint(i))
}

func ManyGo() {
   var wg sync.WaitGroup
   for i := 0; i < 5; i++ {
      wg.Add(1)
      go func(j int) {
         defer wg.Done()
         hello(j)
      }(i)
   }
   wg.Wait()
}

协程Goroutine之间的通信:提倡通过通信共享内存而不是通过共享内存而实现通信 image.png

Channel

无缓冲通道(同步通道):使用无缓冲通道会导致发送的Goroutine和接收的Goroutine同步化 image.png

带缓冲的通道可以解决生产和消费速度不均衡的问题(生产者的逻辑较为简单,生产速度较快;消费者的逻辑较为复杂,消费速度较慢;使用带缓冲的通道就不会因为消费者的消费速度影响生产的生产速率) image.png

并发安全Lock

  • 通过共享内存实现通信:会发生数据硬态
  • 并发安全问题有一定概率引起错误结果出现,所以在项目开发中应该避免对共享内存做一些非并发安全的读写操作

image.png

WaitGroup

开启协程+1;执行结束-1;主协程阻塞直到计数器为0 image.png

依赖管理

背景

image.png

Go依赖管理演进

image.png

GOPATH

环境变量$GOPATH:是一个go项目的工作区

  1. bin:项目编译的二进制文件
  2. pkg:项目编译的中间产物,加速编译
  3. src:项目源码

image.png

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

image.png

Go Vendor

  • 增加一个vendor文件保存所有依赖包的副本
  • 依赖寻址方式:优先从vendor中获取,如果没有的话从gopath中获取
  • 通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。

image.png

弊端(归根结底vendor也是项目的源码,并不能很清晰的标识依赖版本的概念)

  • 无法控制依赖的版本
  • 更新项目又可能出现依赖冲突,导致编译错误

image.png

Go Module

终极目标:定义版本规则和管理项目依赖关系 image.png

依赖管理三要素

  • 配置文件,描述依赖
  • 中心仓库管理依赖库
  • 本地工具

image.png

依赖配置 - go.mod

  • 如果某个包想被人单独引用的话,那就要在每个包的目录下都建一个go.mod
  • 原生库:依赖的原生 Go SDK 版本
  • 依赖标识:[Module Path][Version/Pseudo-version]

image.png

依赖配置 - version

语义化版本

  • marjor:属于一个大版本,不同的marjor之间可以是不兼容的(不同的marjor之间是代码隔离的)
  • minor:新增函数或者功能,需要保持在major下做到前后兼容
  • patch:做代码bug修复

基于commit伪版本

  • 版本前缀 + 提交版本的时间戳 + 12位哈希前缀(哈希校验码)

image.png

依赖配置 - indirect(关键字)

没有直接导入的依赖模块会通过indirect标识(非直接依赖) image.png

依赖配置 - incompatible(关键字)

  • 对于主版本(major)在v2以上的模块会在模块路径增加/vN后缀
  • 对于没有go.mod文件并且主版本(major)在v2以上的依赖,会 + incompatible(表示可能存在不兼容的代码逻辑)

image.png

依赖配置 - 依赖图

选择最低的兼容版本(如果此项目中存在C1.5,他也会选择C1.4image.png

依赖分发 - 回源

  • 解决问题:1. 依赖如何下载; 2. 依赖去哪里下载
  • go的依赖大部分托管在github上,对于go.mod中定义的依赖,则可以从对应仓库中下载指定依赖从而完成依赖分发
  • 直接下载依赖的弊端
    • 无法保证构建稳定性(比如:代码作者可以直接在github上增加/修改/删除版本)
    • 无法保证依赖的可用性(比如:代码作者直接删除github代码仓库,导致代码不可用)
    • 增加第三方压力(比如:人人都从github上下载依赖,导致github负载压力,不满足初衷)

image.png

依赖分发 - Proxy

Proxy是一个服务站点,会缓存依赖代码内容,构建时会直接从Proxy站点中拉取依赖,保证稳定性和可靠性(项目开发中没有什么是不能用Proxy解决的,一层Proxy不行就两次Proxy) image.png

依赖分发 - 变量 GOPROXY

  • Go Module通过GOPROXY环境变量配置Proxy
  • GOPROXY是Proxy(服务站点)URL列表,“direct”表示源站
  • 下载依赖的顺序:首先会从第一个url(proxy1)中下载依赖,如果proxy1不存在,就会从第二个url(proxy2)中下载依赖,如果都不存在,遇到“direct”就会从源站中下载依赖

image.png

工具 - go get

image.png

工具 - go mod

在每次执行项目之前都可以使用go mod tidygo mod tidy的作用是把项目所需要的依赖添加到go.mod,并删除go.mod中,没有被项目使用的依赖。 image.png

测试

测试的三种类型

【从上到下,覆盖率逐层变大,成本却逐层降低,所以一定程度上,单元测试决定着代码质量】

  • 回归测试:通过手动通过终端回归一些固定的主流场景(比如刷刷抖音看看评论ok不ok)
  • 集成测试:对系统功能维度做测试验证,自动化的服务测试
  • 单元测试:面对开发阶段,开发者对单独的函数模块做功能验证

image.png

单元测试的步骤

测试结果与期望结果进行校对,保证质量,提高效率 image.png

单元测试 - 规则

image.png

单元测试 - 列子

image.png

image.png 【开源assert包】 image.png

单元测试 - 覆盖率

解决问题

  • 如何衡量代码是否经过了足够的测试?
  • 如何评价项目的测试水准?
  • 如何评估项目是否达到了高水平测试等级?

实现方法:加上--cover image.png

Tips

  • 一般覆盖率:50% ~ 60%,较高的覆盖率80%+
  • 测试分支相互独立、全面覆盖
  • 测试单元粒度足够小,函数单一职责

单元测试 - 依赖

  • 幂等:多次测试结果一样
  • 稳定:单元测试相互隔离

image.png

不用mock测试,测试时可能会改变测试文件的数据

  • 用函数a替换原函数b(a:打桩函数)
  • patch:打桩
  • unpatch:去掉

image.png

通过patch打桩测试,可不再依赖本地文件 image.png

--- 执行该代码时可能会遇到以下问题 image.png 解决方案: image.png 具体步骤: 未命名3.png 未命名3.png

基准测试

基准测试指测试一段程序的性能及cpu损耗

  • 优化代码,需要对当前代码分析
  • 内置的测试框架提供了基准测试的能力

基准测试的规则

  • 基准测试以Benchmark为前缀
  • 需要一个*testing.B类型的参数b
  • 基准测试必须要执行b.N次

rand在高并发场景如果不注意底层实现的话可能会造成性能问题

  • 解决方法:可以考虑使用fastrand image.png