1 语言进阶
并发: 多线程程序在一个核心的CPU上运行
并行: 多线程程序在多个核的CPU上运行
1.1 Goroutine
Go语言实现高并发的一个基本概念是协程。协程实质上是轻量级的线程,本身的调度由Go语言自身机制来完成。线程本身属于系统内核,线程可以跑多个协程。
协程的栈为KB级别,而线程的栈为MB级别。
想要开启一个协程,只需要在函数执行前添加一个go关键字即可。
1.2 CSP (Communicating Sequential Process)
中译名为通信顺序处理。Go语言提倡通过通信共享内存而非通过共享内存实现通信。下图是通信过程的示意图。
1.3 Channel(通道)
具体操作:make(chan 元素类型, [缓冲大小])
- 对于无缓冲通道:
make(chan int) - 对于有缓冲通道:
make(chan int,2)
无缓和有缓的区别:无缓没有延迟,有缓有一定的延迟。示意图如下图所示。
1.4 并发安全 Lock
实际上Go语言也保留了通过共享内存实现通信的机制。如果不对临界区加以保护,在多线程执行时可能会出现冲突,音系我们可以使用lock函数对其进行保护。
1.5 WaitGroup
Go语言中使用WaitGroup来实现并发任务的同步。函数名:wg.
WaitGroup暴露出来三个方法:
Add(delta int):开启:计数器+deltaDone:执行结束:计数器-1Wait:主协程阻塞:直到计数器为0
2 依赖管理
这里的依赖指的是各种开发包,也即SDK。
2.1 Go 依赖管理的演进
实际上,Go语言的依赖大致可以分为三个阶段:
- GOPATH
- Go Vendor
- Go Module 当前应用最广泛的是Go Module。
2.1.1 GOPATH
GOPATH是Go语言支持的一个环境变量,可被看作是Go语言的工作区。
在$GOPATH下存放三个目录:
- bin:项目编译的二进制文件
- pkg:项目编译的中间产物,可以加速编译
- src:项目的源码,是项目代码的直接依赖。执行
go get指令会下载最新版本的包到该目录下。
弊端:无法实现package的多版本控制。
2.1.2 Go Vendor
改进的依赖管理在项目的目录下增加了vendor文件夹,所有的依赖包以副本的形式存放在$ProjectRoot/vendor目录下。此时项目寻找依赖会优先从vendor目录下去找,如果这里没有会回到GOPATH去找。
实际上,Go Vendor是通过每个项目引入一个依赖的副本来解决多版本冲突的问题。但是,又会存在大包依赖的两个小包中版本不兼容的问题。
2.1.3 Go Module
是Go语言官方推出的依赖管理系统,目标是定义版本规则和管理项目的依赖关系。Go Module通过go.mod文件管理依赖包的版本,go get/go mod指令工具来管理依赖包。
2.2 依赖管理三要素
Go语言实现依赖管理离不开以下三个方面:
go.mod:配置文件,描述依赖Proxy:中心仓库管理依赖库go get/go mod:本地工具
注意: 假如某A项目依赖了两个子项目,这两个子项目同时依赖了某B项目的不同版本,那么在最终编译时选择的某B项目版本是最低的兼容版本。
在构建依赖时,如果选择直接从代码托管平台(Github)上下载源码,存在着一定的问题:
- 无法保证构建的稳定性
- 无法保证依赖的可用性
- 可能增加第三方平台的压力
因此,我们引入
Proxy来缓存相应的依赖。
Go语言通过配置GOPROXY环境变量来管理Proxy。实质上是URL列表,查找时会线性查找。
go get工具默认会拉取major版本的最新提交。go mod工具中,init用于初始化创建go.mod文件,download会下载模块到本地缓存,tidy可以增加需要的依赖且删除不需要的依赖。
3 测试
测试一般分为三种类型:回归测试、集成测试和单元测试。
- 回归测试:直接应用主流场景下的测试
- 集成测试:系统功能维度的测试
- 单元测试:开发者对单独的函数模块进行测试 从上到下:测试成本逐渐降低,覆盖率逐渐上升。
3.1 单元测试
单元测试包含以下部分:
- 输入
- 测试单元
- 函数
- 模块等
- 输出
- 与期望输出的校对 单元测试可以保证工程的质量,也可以提升修改bug的效率
3.1.1 规则
- 所有测试文件均以
_test.go结尾 - 函数命名:
func TestXxx(*testing.T) - 初始化逻辑放到函数
TestMain中:- 测试前进行数据装载、配置初始化等前置工作
- 使用
code := m.Run()执行测试 - 测试后进行释放资源等收尾工作 依据上述格式编写的单元测试代码,在GoLand里面是可以被识别出来的,并且可以实现自动配置。
3.1.2 代码覆盖率
是单元测试的衡量标准。
提升方法:采用尽可能多的测试用例进行测试。
一些小Tips:
- 一般的覆盖率:50~60%,较高的80%+
- 测试分支相互独立、全面覆盖
- 测试单元粒度足够小,函数仅单一职责
3.2 依赖
单元测试中的Mock是一种用于模拟(或替代)被测试代码中的依赖项的技术,可以确保测试过程的隔离性和可控性。
常用的Mock测试框架为monkey。monkey 是 Go 语言中的一个Mock框架,它允许你在运行时修改函数或方法的行为,以便在单元测试中模拟外部依赖,且不需要修改原始代码。这种方法通常被称为 "Monkey Patching",而 monkey 就是为了实现这个目标而创建的工具。它提供了一种非侵入式的方式来修改函数的行为,通过在运行时动态地替换函数的实现,来实现对函数的模拟。利用monkey可以摆脱对测试文件的依赖。
3.3 基准测试
基准测试的测试函数开头为Benchmark。
为了提高基准测试的并行性能,可以选用函数fastrand。但是该函数损失了随机数列的一致性。
4 项目实战:社区话题页面的web服务实现
4.1 需求描述
- 展示话题(标题、文字描述)和回帖列表
- 仅实现本地的Web服务
- 话题和回帖数据用文件形式存储
4.2 需求分析
访问页面时会涉及到两个元素:Topic、PostList。二者是一对多的关系。以下是一个ER图(Entity Relationship Diagram):
4.3 分层结构
这个项目可以划分为三个层面:
- 数据层:数据Model,实现外部数据的增删改查
- 逻辑层:业务Entity,处理核心业务的逻辑输出
- 视图层:视图view,处理和外部的交互逻辑,通过API的方式输出处理结果 具体的分层结构如下图: