GO上手实践
并发 VS 并行
并发
多线程程序在一个核的CPU上运行
并行
多线程程序在 多个核的CPU上运行
go可以充分发挥多核优势,高效运行
Goroutine
协程:用户态,轻量级线程,栈MB级别。
线程:内核态,线程跑多个协程,栈KB级别。
eg:开启协程
func hello(i int){
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine(){
for i:=0; i < 5; i++;{
go func(j int) {
hello(j);
}(i)
}
time.Sleep(time.Second)
}
CSP(communicating Swquential Processes)
GO语言提倡通过
通信共享内存
而不是通过共享内存而实现通信
Channel
make(chan 元素类型,[缓冲大小])
-
无缓冲通道 make(chan int)
-
有缓冲通道 make(chan int,2)
eg:
A子协程发送0~9数字
B子协程计算输入数字的平方
主协程输出最后的平方数
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
对变量执行2000次+1操作,5个协程并发执行
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() {
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
计数器
开启协程+1;执行结束-1;主协程阻塞直到计数器为0.
func ManyGowait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func() {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
依赖管理
GOPATH
- 环境变量$GOPATH
- 项目代码直接依赖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
依赖配置-go.mod
module example/project/app //依赖管理基本单元
go 1.16//原生库
require (
//单元依赖
example/lib1 v1.0.2
example/lib2 v1.0.0 // indirect
example/lib3 v0.1.0-20190725025543-5a5fe074e612
example/lib4 v0.0.0-20180306012644-bacd9c7ef1dd// indirect
example/lib5/v3 v3.0.2
example/lib6 v3.2.0+incompatible
)
依赖标识:[Module Path] [Version/Pseudo-version]
依赖配置-version
语义化版本
-
M
A
J
O
R
.
{MAJOR}.
MAJOR.{MINOR].${PATCH}
eg:V1.3.0 V2.3.0
基于commit伪版本 - v×.0.0-yyyymmddhhmmss-abcdefgh1234
eg:v0.0.0-20220401081311-c38fb59326b7
v1.0.0-20201130134442-10cb98267c6c
依赖配置- indirect
使用indirect标明间接依赖模块
依赖配置- incompatible
- 主版本2+模块会在模块路径增加/vN后缀。
- 对于没有go.mod文件并且主版本2+的依赖,会+incompatible
依赖分发- 回源
- 无法保证构建稳定性增加/修改/删除软件版本
- 无法保证依赖可用性删除软件
- 增加第三方压力
代码托管平台负载问题
中心仓库管理依赖库 Proxy
依赖分发-变量GOPROXY
GOPROXY="https://proxy1.cn,https://proxy2.cn,direct服务站点URL列表,“direct”表示源站
本地工具 go get/mod
测试
从上到下,覆盖率逐层变大,成本却逐层降低
单元测试
单元测试-规则
-
所有测试文件以_test.go结尾
-
func TestXxx(*testing.T)
-
初始化逻辑放到TestMain中
eg:
单元测试-assert
单元测试-覆盖率
如何衡量代码是否经过了足够的测试?
如何评价项目的测试水准?
如何评估项目是否达到了高水准测试等级?
代码覆盖率
单元测试-Tips
- 一般覆盖率:50%~60%,较高覆盖率80%+。
- 测试分支相互独立、全面覆盖。
- 测试单元粒度足够小,函数单一职责。
单元测试-依赖
单元测试 - Mock
monkey : https://github.com/bouk/monkey
快速Mock函数
- 为一个函数打桩
- 为一个方法打桩
基准测试
基准测试-例子
基准测试-运行
基准测试-优化
项目实践
需求设计
需求背景
需求描述
- 展示话题(标题,文字描述)和回帖列表
- 暂不考虑前端页面实现,仅仅实现一个本地web
- 服务话题和回帖数据用文件存储
需求用例
ER图-Entity Relationship Diagram
分层结构
- 数据层:数据Model,外部数据的增删改查
- 逻辑层:业务Entity,处理核心业务逻辑输出
- 视图层:视图view,处理和外部的交互逻辑
组件工具
- Gin高性能go web框架
https://github.com/gin-gonic/gin#installation - Go Mod
go mod init
go get gopkg.in/gin-gonic/gin.v1@v1.3.0
Repository
Repository-index
var(
topicIndexMap map[int64]*Topic
postIndexMap map[int64][]*Post
)
Repoditory-查询
Service
Controller
Router
运行
运行测试
go run server.go