Go语言进阶|青训营笔记

122 阅读5分钟

Go语言进阶|青训营笔记

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

一、本堂课重点内容

  • Go语言的进阶:并行处理、Goroutine,channel
  • 并发锁
  • 依赖管理:go.mod
  • 测试

二、详细知识点介绍

1. 语言进阶

1.1 并发 vs 并行

  • 并发:多线程程序在单核cpu运行
  • 并行:多线程程序在多核cpu运行
  • go可以发挥多核优势并行,利用调度充分利用资源

1.2 Goroutine

  • 协程:用户态、轻量级线程、栈MB级别
    • go语言一次可创建上万协程
  • 线程:内核态、线程跑多个协程,栈KB级别
  • 使用:在调用函数时前面加go关键字
    • 为函数创建协程运行

1.3 communicating sequential processes

  • 提倡通过通信共享内存,而不是通过共享内存实现通信
  • 没有临界区 而是使用通道 Screen Shot 2023-01-15 at 11.25.48 PM.png
  • Channel通道
    • make(chan 元素类型, [缓冲大小])
    • 无缓冲通道:make(chan int)
    • 有缓冲通道:make(chan int,2)
    • 生产->消费:并发安全,能够保证执行的顺序
      • 消费者消费速度慢,可以使用带缓冲channel解决生产者和消费者的速度不一致的问题

1.4 并发安全锁

  • 使用lock保证并发安全
    lock sync.Mutex
    
    //保障并发安全性
    lock.Lock()
    xxx
    lock.Unlock()
    

1.5 WaitGroup

  • 计数器
    • 开启协程+1 Add(delta int)
    • 执行结束-1 Done()
    • 主协程阻塞直到计数器为0 Wait()

2.依赖管理

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

2.1 背景

  • 单体函数组成复杂项目
  • 工程项目不能基于标准库
  • 管理依赖库

2.2 Go依赖管理的演进

  • 不同环境依赖的版本不同
  • 控制依赖库的版本
    • GOPATH:
      • 环境变量$GOPATH
        • bin, pkg, src
      • 项目代码直接依赖src下的代码
      • 通过go get下载最新版本的包到src目录下
      • 弊端:若A B项目依赖同一package的不同版本,则无法实现package的多版本控制
    • GO Vendor
      • 项目目录增加vendor文件,依赖包以副本形式放在$ProjectRoot/vendor
      • 依赖寻址方式:vendor -> GOPATH
      • 通过每个项目引入一个依赖的副本解决多个项目需要同一个package依赖的冲突问题
      • 弊端:无法控制依赖的版本,更新项目可能导致依赖冲突从而编译出错
    • GO Module
      • 通过go.mod文件管理依赖包版本
      • 通过go get/go mod指令工具管理依赖包
      • 定义版本规则和惯例项目依赖关系
  • 依赖管理三要素
    • 配置文件描述依赖的包 go.mod
    • 中心仓库管理依赖库 proxy
    • 本地工具 go get/mod

2.3 依赖配置 - go.mod

  • 类似maven
    //依赖管理基本单元
    module xxx/project/app
    
    //原生库
    go 1.16
    
    //单元依赖
    require(
        xxx/lib1 v1.0.2
        xxx/lib2 v1.0.0 //indirect
        ...
    )
    
  • 依赖标识: [Module Path][Version/Pseudo-version]
  • version
    • 语义化版本:v 1.1.1
    • 基于commit的伪版本:v X.0.0-yyyymmddhhmmss-xfhdfgewuif
  • indirect
    • 间接依赖标识,没有直接导入的
  • incompatible
    • 主版本2+后的模块会在模块路径增加vN后缀
    • 对于没有go.mod文件并且主版本2+后的依赖会+incompatible
  • 若两项目分别依赖一个项目的两个不同版本,则在编译时,会选择最低的兼容版本

2.4 依赖分发 - 回源

  • 寄存代码在第三方托管平台(github)问题
    • 无法保证构建稳定性
      • 可以在代码平台进行增删改版本
      • 在下次使用依赖时发生问题
    • 无法保证依赖可用性
    • 增加第三方压力
      • 代码托管平台的负载问题
  • Proxy
    • 缓存原版本的依赖内容
    • 若源平台修改删除相关依赖,可以从Proxy获得相关内容
  • GOPROXY

GOPROXY = "proxy1.cn, proxy2.cn, direct" 服务站点的URL列表 -> 源网站

2.5 go get工具

  • go get example.org/pkg + 下列tag
    • @update 默认
    • @none 删除依赖
    • @v1.1.2 tag版本
    • @23dff5 拉取特定commit版本
    • @master 分支最新commit

2.6 go mod工具

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

3. 测试

  • 回归测试:手动通过终端进行测试
  • 集成测试:系统功能维度进行测试
  • 单元测试:测试开发阶段对于单独模块进行测试验证
  • 覆盖率逐渐变大,成本降低

3.1 单元测试 Unit test

  • go test xxx_test.go xxx.go --cover
  • 组成部分
    • 输入-测试单元-输出
    • 与期望进行校对
  • 规则
    • 所有测试文件以_test.go结尾
    • func TestXxx(*testing.T)
    • 初始化逻辑放到TestMain中
      • code:= m.run()
  • 单元测试assert
    • assert.Equal
    • 修改对应位置
  • 覆盖率
    • 衡量代码经过了足够的测试,评估项目的测试水准
    • 提升代码测试覆盖率,测试更多行的代码
    • 一般项目覆盖率 50-60%,较高为80%+
    • 测试分支相互独立,全面覆盖
    • 测试单元粒度足够小,函数单一职责

3.2 测试依赖

  • 外部依赖 -> 稳定&幂等

3.3 文件处理测试

  • 测试依赖及本地文件

3.4 Mock 为一个函数/方法打桩

  • 包装原函数,replace为另一个函数,unpatch卸载
  • 打桩测试不再依赖本地文件
  • 保证测试可以在任何环境进行执行

3.5 基准测试

  • 优化代码,需要对代码分析
  • 内置测试框架支持对代码的分析
  • BenchmarkXxx(* testing.B)
  • 同时测试Parallel情景下的性能
    • 对比后发现性能偏差,进行优化

三、实践练习例子

  • 需求:社区话题页面
    • 展示话题(标题、文字描述)、回帖
    • 本地web服务
    • 话题和回帖使用文件存储
  • 分层结构
    • 数据层:数据Model,外部数据增删改查
    • 逻辑层:业务Entity,处理核心业务逻辑输出
    • 视图层:视图view,处理和外部的交互逻辑
  • 代码

四、课后个人总结

本堂课主要介绍了并行编程的相关内容,介绍了go语言定义依赖的相关信息,以及测试的内容。在实践例子中对分层实现项目进行了简单介绍。本堂课主要的难点在于并行的实现以及并行锁的相关使用。

五、引用参考