这是我参与【第五届青训营】伴学笔记创作活动的第2天
一、本堂课重点内容
- 语言进阶:从并发编程的视角了解Go高性能的本质
- 依赖管理:了解Go语言依赖管理的演进路线
- 测试:从单元测试实现出发,提升质量意识
- 项目实战:通过项目需求、需求拆解、逻辑设计、代码实现感受真实的项目开发
二、详细知识点介绍
1、语言进阶——并发编程
并发 VS 并行
并发:多线程程序在一个核的cpu上运行 并行:多线程程序在多个核的cpu上运行
Goroutine
协程 :用户态,轻量级线程,栈MB级别
线程:内核态,线程跑多个协程,栈KB级别
协程例子
【快速打印hello goroutine:0~hello goroutine:4】
package main
import (
"fmt"
"time"
)
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)
}
func main() {
HelloGoRoutine()
}
运行结果:
CSP (Communicating Sequential Processes)
通过通信共享内存 通过共享内存实现通信(推荐)
Channel
例子:make(chan 元素类型, [缓冲大小])
无缓冲通道 make(chan int)
有缓冲通道 make(chan int, 2)
使用例子
A 子协程发送 0 - 9 数字
B 子协程计算输入数字的平方
主协程输出最后的平方数
package main
func main() {
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个协程并发执行
不加锁时:结果输出的是未知的(并发安全问题!)
加锁时:结果输出正确
sync
waitgroup
实现并发安全操作和协程间的同步
2、依赖管理
GOPATH
-
bin:项目编译生成的二进制文件
-
pkg:项目编译的中间产物,加速编译
-
src:项目源码
弊端:无法实现package的多版本控制
Go Vendor
通过每个项目引入一份依赖的副本,解决多个项目需要同一个package依赖的冲突问题
当vender目录下没有相关依赖时,再到gopath中去找
弊端:无法控制依赖的版本,更新项目又可能出现依赖冲突,导致编译出错
Go Module
通过go.mod文件管理依赖包版本,通过go get/go mod指令工具管理依赖包
3、测试
从上到下,覆盖率逐层变大,成本却逐层降低
单元测试
规则:
- 所以测试文件以_test.go结尾
- func TestXxx(*testing.T)
- 初始化逻辑放到TestMain中
覆盖率:
- 一般覆盖率: 50%一60%,较高覆盖率80%+
- 试分支相互独立、全面覆盖
- 测试单元粒度足够小,函数单一职责
Mock测试库
基准测试
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力
4、项目实践
需求描述
社区话题页面
√ 展示话题 (标题,文字描述)和回帖列表
√ 暂不考虑前端页面实现,仅仅实现一个本地web服务
√ 话题和回帖数据用文件存储
需求用例
设计ER图
分层结构
数据层:数据 Model,外部数据的增删改查
逻辑层:业务 Entity,处理核心业务逻辑输出
视图层: 视图 view,处理和外部的交互逻辑
组件工具
- Gin 高性能 go web 框架 github.com/gin-gonic/g…
- Go Mod go mod init go get
Respository
例子:
var(
topicIndexMap map[int64]*Topic
postIndexMap map[int64][]*Post
)
Respository-index
初始化话题数据索引例子:
func initTopic IndexMap(filePath string) error {
open,err := os.Open(filePath + "topic"
if err != nil{
return err
}
scanner := bufio.NewScanner(open)
topicTmpMap := make(map[int64]*Topic)
for scanner.Scan() {
text := scanner.Text(0var topic Topic
if err := json.Unmarshal([]byte(text), &topic); err != nil {
return err
}
topicTmpMap[topic.Id] = &topic
}
topicIndexMap = topicTmpMap
return nil
}
Respository-查询
例子: 索引:话题ID
数据:话题
type Topic struct {
Id int64 `json:"id"`
Title string `json:"titler"`
Content string `json:"content"`
CreateTime int64 `json:"create_time"`
}
type TopicDao struct {
}
var(
topicDao *TopicDao
topicOnce sync.Once
)
func NewTopicDaoInstance() *TopicDao {
topicOnce.Do(
func() {
topicDao = &TopicDao{}
})
return topicDao
}
func (*TopicDao) QueryTopicById(id int64) *Topic {
return topicIndexMap[id]
}
Service
代码流程编排
Controller
构建View对象
业务错误码
Router
初始化数据索引
初始化引擎配置
构建路由
启动服务
运行
go run server.go
三、课后个人总结
项目实战中了解到各个部分的项目分工,代码拆解的思路很好,我们做项目时可以将大需求化小需求来分析。
四、课程链接学习
五、其他阅读参考学习
1.锁Lock