这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
go工程实践
并发与并行
goroutine
线程:处于内核态,一个线程跑多个协程,栈MB级别 协程:处于用户态,轻量级线程,栈KB级别
// 为调用的函数创建一个协程来执行
//使用go关键字
func HelloGoRoutine() {
for i:=0;i < 5>; i++ {
go func(j int) { // 创建协程
hello(j) // 被调用的函数
}(i)
}
}
go中协程间的通信
go提倡通过通信共享内存,而不是通过共享内存来完成通信 通信的方式:通道(channel)
go中的通道channel
make(chan 元素类型,【缓冲大小】)
- 无缓冲通道 make(chan int)
- 有缓冲通道 make(chan int, 2)
// A子协程发送0~9数字
// B子协程计算输入数字的平方
// 主协程输出最后的平方数
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() { // A子协程
defer close(src)
for i := 0; i < 10; i++ {
sec <- i
}
}()
go func() {
defer close(dest)
for i := range(src) {
dest <- i * i
}
}()
for i := range(dest) {
fmt.Println(i)
}
}
并发安全 Lock
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()
}
fmt.Println("withoutlock", x)
x = 0
for i := 0; i < 5>; i++ {
go addWithLock()
}
fmt.Println("withlock", x)
}
使用计数器来搞定不知道子线程何时结束
WaitGroup包
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5) // 计数器初始化为5,因为
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
工程项目
gopath
gopath是有弊端的,有兼容问题,所有的源码都处于src目录下(bin目录为编译后的二进制文件,pkg目录为项目中编译的中间产物,加速编译 )。如果多个项目依赖不同版本的package,那么gopath无法做到
govendor
依旧依靠的是源码,无法控制依赖的版本
gomodule
通过go.mod文件管理依赖包版本 通过go get/go mod指令工具管理依赖包
go get
go get exanple.org/plg @maseter 获取分支的最新commit
go mod
go mod init 初始化,创建go.mod文件
go download 下载模块到本地缓存
go mod tidy 增加需要的依赖,删除不需要的依赖
测试
测试的种类
- 回归测试 QA测试人员
- 集成测试 自动化测试
- 单元测试 开发人员
单元测试的规则
- 所有测试文件以_test.go结尾
- 测试函数为
func TestXxxx(*testing.T) - 初始化逻辑放到TestMain中
import (
"github.com/stretchr/testify/assert"
"testing"
)
// 被测试的函数
func HelloTom() string {
return "Jerry"
}
//测试函数
func TestHelloTom(t *testing.T) {
output := HelloTom()
expectOutput := "Tom"
assert.Equal(t, output, expectOutput)
}
单元测试-覆盖率
- 一般覆盖率:50%~60%,较高覆盖率80%+
- 测试分支相互独立、全面覆盖
- 测试单元粒度足够小,函数单一职责
单元测试-mock
打桩函数:采用替换的思想,将原始数据替换成虚拟的数据
package test
import (
"bou.ke/monkey"
"github.com/stretchr/testify/assert"
"testing"
)
func TestProcessFirstLine(t *testing.T) {
firstLine := ProcessFirstLine()
assert.Equal(t, "line00", firstLine)
}
func TestProcessFirstLineWithMock(t *testing.T) {
monkey.Patch(ReadFirstLine, func() string {
return "line110"
})
defer monkey.Unpatch(ReadFirstLine)
line := ProcessFirstLine()
assert.Equal(t, "line000", line)
}
项目实战
需求描述
- 展示话题(标题,文字描述)和回帖列表
- 暂不考虑前端页面实现,仅仅实现一个本地web服务
- 话题和回帖数据用文件存储
组件工具
介绍下开发涉及的基础组件和工具,首先是gin, 高性能开源的go web框架,我们基于gin搭建web服务器,在课程手册应该提到了,这里我们只是简单的使用,主要涉及路由分发,不会涉及其他复杂的概念。
因为我们引入了web框架,所以就涉及go module依赖管理,如前面依赖管理课程内容讲解,我们首先通过go mod是初始化go mod管理配置文件,然后go get下载gin依赖,这里显示用了V1.3.0版本。
有了框架依赖,我们只需要关注业务本身的实现,从reposity --> service --> contoller我们一步步实现。希望大家能跟上我的节奏,从0~1 实现这个项目,如果时间问题,大家可以一步步copy一下,主要是走一半开发思路。
分层结构
整体分为三层,repository数据层,service逻辑层,controller视图层 数据层关联底层数据模型,也就是这里的model,封装外部数据的增删改查,我们的数据存储在本地文件, 通过文件操作拉取话题, 帖子数据;数据层面向逻辑层,对service层透明, 屏蔽下游数据差异,也就是不管下游是文件,还是数据库,还是微服务等,对service层的接模型是不变的。
Servcie逻辑层处理核心业务逻辑,计算打包业务实体entiy,对应我们的需求,就是话题页面,包括话题和回帖列表,并上送给视图层;
Cortroller视图层负责处理和外部的交互逻辑,以view视图的形式返回给客户端,对于我们需求,我们封装json格式化的请求结果,api形式访问就好,
各层的代码逻辑和Java相类似,可以采用协程的方式提高性能
大家在后期做项目开发中,一定要思考流程是否可以并,通过压榨CPU,降低接口耗时,不要一味的串行实现, 浪费多核cpu的资源。
Router
构建路由,将web访问路径与controller的方法相绑定