这是我参与「第五届青训营 」笔记创作活动的第2天。
课程讲授内容
- 并发编程,从并发编程角度了解 Go 的高性能;
- 依赖管理,了解 Go 的依赖演变路线;
- 单元测试,mock 测试和基准测试,提升质量意识;
- 项目实战,项目需求、需求拆解和逻辑设计,感受项目的开发落地。
1 语言进阶
1.0 并发 VS 并行
- 并发:多线程程序在一个核的 cpu 上运行
- 并行:多线程程序在多个核的 cpu 上运行
- Go可以充分发挥多核优势,高效运行
1.1 Goroutine
- 协程:用户态,比较轻量,栈 KB 级别
- 线程:内核态,栈 MB 级别
- 线程可以并发得运行多个协程,Go 在高并发设计中体现了巨大优势。
1.2 CSP (Communicating Sequential Processes)
- 针对共享的问题:提倡通过通信共享内存,而不是通过共享内存实现通信,因为这会发生内容冲突。
1.3 channel
- 声明
make(chan元素类型,[缓冲大小])- 无缓冲通道 make(chan int)
- 有缓冲通道 make(chan int,2)
- 计算平方数案例
- make 两个 channel,
src传输数字,dest保存计算后的平方数
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 } }() - make 两个 channel,
注:
dest为有缓冲通道,目的是防止消费者的消费速度影响生产者的平衡效率,因为通常消费者的消费较为复杂,会变得较慢。
1.4 并发安全问题
避免因共享内存而导致的数据错误,利用互斥锁 sync.Mutex。
如以下示例中对变量进行 2000 次加 1 操作,5 个协程共同进行。
运用锁的结果达到了我们预想的结果,没有使用锁的结果出现异常。这也说明了在共享内容的读写中要注意可能引起数据的混乱。
package main
import (
"fmt"
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func add() {
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
fmt.Println("With Lock:", x)
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
fmt.Println("Without Lock:", x)
}
1.5 waitgroup
sync.WaitGroup 实现了进程的同步(不会像 Mutex 一样暴力阻塞,优雅)。提供了三个方法:
Add(delta int)计数器 +deltaDone()计数器 -1Wait()阻塞直到计数器为 0 计数器:开启协程+1;执行结束-1;主协程阻塞直到计数器为0.
2 依赖管理
为的是将重心放在业务逻辑的实现上。
2.1 Go 依赖演进
GOPATH -> Go Vendor -> Go Module
- 不同环境(项目的)依赖管理
- 控制依赖库的版本
-
GOPATH
- bin 项目编译的二进制文件
- pkg 项目编译的中间产物,加速编译
- src 项目源码
弊端:无法实现 package 的多版本控制。
-
Go Vendor
- 项目目录下增加vendor文件,所有依赖包副本形式放在
$ProjectRoot/vendor - 依赖寻址方式:vendor =>GOPATH,先到 vendor 下去寻找是否有依赖,没有的话再到 GOPATH 去找。
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个 package依赖的冲突问题。
弊端:①无法控制依赖的版本。②更新项目又可能出现依赖冲突,导致编译出错。
- 项目目录下增加vendor文件,所有依赖包副本形式放在
-
Go Module
- 通过
go.mod文件管理依赖包版本 - 通过
go get/go mod指令工具管理依赖包
- 通过
2.3 依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
2.4 Go Module 工具
2.4.1 go get
-
go get xx.org/pkg
@update默认@none删除依赖@v1.1.2tag版本,语义版本@23dfdd12特定的 commit@master分支的最新 commit
-
go mod
init初始化,创建 go.mod 文件download下载模块到本地缓存tidy增加需要的依赖,删除不需要的依赖
3 单元测试
测试时避免事故的最后一道屏障。
通常有三种测试:回归测试、集成测试和单元测试。
3.1 单元测试
重要:GitHub限流,参考 goproxy.cn/ 配置第三方依赖包。
- 通过输入到测试单元,得到输出,和期望值进行校对,以提升效率和保证质量。
- 测试单元包括函数、模块等。
3.1.1 单元测试 - 规则
- 所有测试文件以
_test.go结尾。 - 测试函数:
func TestXxx(t *testing.T),函数名需要以Test开头,其中t参数用于报告测试失败和附加的日志信息。 - 初始化逻辑放到
TestMain中。
3.1.2 单元测试 - 运行
- 命令
go test [flag] [package],go test命令如果没有参数指定包那么将默认采用当前目录对应的包。- 常用参数:
-v用于打印每个测试函数的名字和运行时间;-run对应一个正则表达式,只有测试函数名被它正确匹配的测试函数才会被go test测试命令运行;
- 常用参数:
- assert 包中的 Equal 可判断期望输出和测试结果是否一致。
- 案例示例
- 编写待测试函数
package gotest func callTom() string { return "Tom" }- 编写测试函数
package gotest import "testing" func TestEqualString(t *testing.T) { output := callTom() expectedOutput := "Tom" if output != expectedOutput { t.Errorf("Expected %s is not output %s", expectedOutput, output) } }go test测试结果
$ go test PASS ok github.com/Moonlight-Zhao/go-project-example/myDemo/gotest 0.494s
3.1.3 单元测试 - 覆盖率
- 命令:
test git:(V0) x go test judgment_test.go judgment.go --cover
4 项目实战
4.1 需求
- 展示话题(标题,文字描述)和回帖列表
- 暂不考虑前端页面实现,仅仅实现一个本地web服务
- 话题和回帖数据用文件存储
4.2 话题和帖子结构体
- 话题 Topic
- id
- title
- content
- create_time
- 帖子 Post
- id
- topic_id
- content
- create_time
4.3 分层结构
- 数据层:数据Model,外部数据的增删改查
- 逻辑层:业务Entity,处理核心业务逻辑输出
- 视图层:视图view,处理和外部的交互逻辑
4.4 组件工具
- Gin高性能go web框架:github.com/gin-gonic/g…
- Go Mod:
go mod initgo get gopkg.in/gin-gonic/gin.v1@v1.3.0
4.5 Repository - index
- 增加索引,很快定位数据
var (
topicIndexMap map[int64]*Topic
postIndexMap map[int64][]*Post
)
4.6 server
- 实体
type PageInfo struct {
Topic *repository.Topic
PostList []*repository.Post
}
- 流程:参数校验->准备数据->组装实体
4.7 Controller
- 构建 View 对象
- 业务错误码
4.8 Router
- 初始化数据索引
- 初始化引擎配置
- 构建路由
- 启动服务
课程总结
收获:了解了 Goroutine 实现并发,在共享内存和通信方面知晓了提倡使用通过通信实现内存共享;了解了通过 channel 实现数据共享;还有 mutex(避免因共享内存而导致的数据错误) 和 waitgroup(实现阻塞的优雅方式)。除此之外还有依赖的相关发展和管理以及测试;通过项目实战将前面所学的知识融合,巩固知识,增强编程能力。
存在问题:并发编程和测试方法还需要多实践,项目实战有很多地方还不明白,争取再梳理一遍项目实现过程。