这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
并发编程
- 并发:多线程程序在一个核的CPU上运行,主要通过时间片的切换来实现同时运行的一个状态
- 并行:多线程程序在多个核的CPU上运行,是实现并发的一个手段
Go可以充分发挥多核优势,高效运行
- 协程:用户态,轻量级线程,栈MB级别
- 线程:内核态,线程跑多个协程,栈KB级别
示例程序
package main
import (
"fmt"
"time"
)
func hello(i int) {
println("hello goroutine : " + fmt.Sprint(i))
}
func main() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
CSP(Communicating Sequential Processes)
提倡通过通信共享内存而不是通过共享内存实现通信
Channel
make(chan 元素类型,[缓冲大小])
- 无缓冲通道
make(chan int)致使两个协程同步化 - 有缓冲通道
make(chan int 2)
示例程序
子协程发送0-9数字
子协程计算输入数字的平方,主协程输出最后的平方数
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)
}
}
并发安全Lock
示例程序
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() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("WithoutLock:", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("WithLock:", x)
}
WaitGroup
- Add(delta int) 计数器+delta
- Done() 计数器-1
- Wait() 阻塞直到计数器为0
计数器:开启协程+1,执行结束-1,主协程阻塞直到计数器为0
依赖管理
依赖管理演进
GOPATH->Go Vendor->Go Module
- 不同环境(项目)依赖的版本不同
- 控制依赖库的版本
-
GOPATH:
-
环境变量$GOPATH
-
项目源码直接依赖src下的代码
-
go get下载最新版本的包到src目录下
弊端:
- 无法实现package的多版本控制
-
-
Go Vendor:
-
项目目录下增加vendor文件,所有依赖包副本形式放在$Projectroot/vendor
-
依赖寻址方式:vendor => GOPATH
-
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题
弊端:
- 无法控制依赖的版本
- 更新项目可能出现依赖冲突,导致编译出错
-
-
Go Module
- 通过go.mod文件管理依赖包版本
- 通过go get / go mod 指令工具管理依赖包
- 终极目标:定义版本规则和管理项目依赖关系
三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
配置文件go.mod
依赖标识:[Module Path] [Version/Pseudo-version]
version
语义化版本:${MAJOR}.${MINOR}.${PATCH}
v1.3.0,v2.3.0
基于commit伪版本:vx.0.0-yyyymmddhhmmss-abcdefgh123
v1.0.0-20220401081311-c38fb59326b7
indirect:标识间接依赖,即没有导入的依赖
incompatible:标识可能存在的不兼容代码逻辑
当有依赖所依赖的依赖版本冲突时,会选择最低的兼容版本。
中心仓库管理依赖库
回源
-
无法保证构建稳定性
增加/修改/删除软件版本
-
无法保证依赖可用性
删除软件
-
增加第三方压力
代码托管平台负载问题
Proxy
GOPROXY
GOPROXY="proxy1.cn,https://proxy2.cn,…"
服务站点URL列表,"direct"标识源站
proxy1->proxy2->direct
若前面的站点都没有依赖,则回源到第三方代码平台
工具
go get
go get example.orh/okg
后接参数:
- @update 默认拉取最新版本
- @none 删除依赖
- @v1.1.2 tag版本,语义版本
- @23dfdd5 特定的commit
- @master 分支最新commit
go mod
go mod
- init 初始化,创建go.mod文件
- download 下载模块到本地缓存
- tidy 增加需要的依赖,删除不需要的依赖
测试
- 回归测试
- 集成测试
- 单元测试
从上到下,覆盖率变大,成本降低
单元测试
规则
- 所有测试文件以
_test.go结尾 func TestXxx(t *testing.T)- 初始化逻辑放在
TestMain中
Tips
- 一般覆盖率:50%-60%,较高覆盖率80%
- 测试分支相互独立、全面覆盖
- 测试单元粒度足够小,函数单一职责
基准测试
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力
方法名以Benchmark 开头
项目实战
需求分析
- 展示话题(标题、文字描述)和回帖列表
- 暂不考虑前端页面实现,仅仅实现一个本地web服务
- 话题和回帖数据用文件存储
需求用例
Topic对Post:一对多
-
Topic
- id
- title
- content
- create_time
-
Post
- id
- topic_id
- content
- create_time
\