这是我参与「第五届青训营 」笔记创作活动的第2天
Day2 Go工程进阶
Go语言进阶
并发 vs并行
并发是实现在单核CPU上的时间片的切分,并发是实现字在多核CPU的配合
Go可以充分发挥多核优势,提高效率
Go routine
协程是用户态(轻量),线程是内核态(昂贵的资源)
go func(...){}()
//开启一个协程
CSP(Computing Sequential Processes)
Go通过通信共享内存(保证收发顺序),但也保留了不太好的通过共享内存来实现通信
Channel
创建:make(chan 元素类型, [缓冲大小])
- 无缓冲通道 make(chan int) —— 收发routine通信时保持同步
- 有缓冲通道 make(chan int, 2) —— 阻塞发送,不同步处理
通过合理使用两种通道,可以合理地解决生产与消费速度不一致的问题。
Sync.Lock
如果使用共享内存实现通信,就需要使用Lock来保证并发安全
Lock()方法 :写锁,如果在添加写锁之前已经有其他的读锁和写锁,则Lock就会阻塞直到该锁可用,为确保该锁最终可用,已阻塞的Lock调用会从获得的锁中排除新的读取锁,即写锁权限高于读锁,有写锁时优先进行写锁定。
Unlock()方法 写锁解锁,如果没有进行写锁定,则就会引起一个运行时错误。
Sync.WaitGroup替代time.Sleep()优雅地实现 并发 同步
WaitGroup有三个方法,Add,Done,Wait
当计数器为0时,表明所有 并发 的协程都执行完毕
Go依赖管理
————多文件项目的组织关系
依赖管理的演进
GOPATH
主要有三部分:bin src pkg
项目代码直接依赖于src下的代码,go get直接下载最新版本到src目录下
————问题:无法实现多版本控制
Go Vender
在项目目录下增加一个vendor目录,在项目寻找依赖时优先从vendor搜索
————问题:仍然会依赖于源码,并没有明确的版本概念
Go Mudule
通过go.mod文件管理依赖包版本
version有语义化版本和commit伪版本
前者分别是:大版本.兼容更新.bug修复
后者分别是:语义化-时间戳-12位hash码
Indirect A->B->C 如果A->C 就叫间接依赖
Incompatible 表示可能存在冲突的包
依赖分发
可能在Github SVN上托管,但是可能无法保证稳定性、可用性,而且增加第三方压力
PROXY
因此我们引入的代理服务站,缓存版本内容,实现稳定可靠的分发
比如我们配置环境时见到的GOPROXY=”url1, url2, direct“
意思就是优先从url1,然后url2,如果都没有就回到源站下载
go mod常用指令
go mod init//初始化go.mod文件
go mod download//下载依赖包——可用tidy包括
go mod tidy//更新包资源(下载和删除)
Go测试
回归测试 ——集成测试——单元测试(从左到右覆盖率逐层变大,成本逐层降低)
单元测试
- 文件后缀:_test.go
- 测试函数:func Test(t *testing.T)
- 初始化逻辑放在TestMain(m *testing.M)中
- 可以自己实现判断逻辑,也可以引入assert包使用
assert.Equal(t, expectOutput, output)
//t为*testing.T类型
- 覆盖率是测试质量的基准,如下代码常被使用
go test judgement_test.go judgement.go --cover
//--cover是为了输出覆盖率
Mock测试
使用monkey包,利用打桩来测试,解除一些依赖场景
比如不再需要关注本地文件是否存在
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)
}
基准测试
做好对比,然后进行优化
rand()在高并发场景下并不好,可用fastrand()替代
项目实践
ER图(Entity Relationship Diagram)
可以认为是数据的抽象体,用图示的方法把项目涉及到的数据组织起来。
分层结构(常用的结构)
框架——Go Gin 高性能 go web框架
常见的项目实现过程:需求分解——逐层解决——充分测试
课后作业
这里我按照原来文件里的那种方法,进行了ID顺次增大的处理,然后增加帖子后返回包括着ID的结构体
主体代码如下,不过有变量名做了更改(因为一些变量如果需要成为全局变量,需要首字母大写)
r.POST("/community/page/topic", func(c *gin.Context) {
defer c.Request.Body.Close()
var pt repository.Topic
data, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
println(err)
}
err = json.Unmarshal(data, &pt)
if err != nil {
println(err)
}
pt.Id = repository.MaxIndex + 1
repository.MaxIndex++
repository.TopicIndexMap[pt.Id] = &pt
pt.CreateTime = time.Now().UnixNano()
jstp, _ := json.Marshal(pt)
string_pt := "\n" + string(jstp)
open, err := os.OpenFile("./data/topic", os.O_APPEND, 0644)
if err != nil {
logrus.Error(err)
open.Close()
}
_, err = open.WriteString(string_pt)
if err != nil {
logrus.Error(err)
}
if err = open.Close(); err != nil {
logrus.Error(err)
}
c.JSON(200, pt)
})