Go 语言进阶 - 工程进阶 | 青训营笔记
这是我参与「第五届青训营 」笔记创作活动的第2天
概览
并发控制
-
go的一个特性就是高并发,这是由其所支持的协程(goroutine)所体现的。
-
go中实现并发控制有两个方式:
-
通过通信实现共享内存(goroutine + channel)
- 其中channel分为无缓冲
make(chan TypeName)和有缓冲make(chan TypeName, bufSize)
- 其中channel分为无缓冲
-
通过共享内存实现通信(传统lock)
-
实现通信进行并发控制
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 { fmt.Println(i) } -
通过共享内存实现通信
var( x int64 lock sync.Mutex ) func AddWithLock() { for i := 0; i < 2000; i++ { lock.Lock() x++ lock.Unlock() } }
-
-
使用WaitGroup完成协程的同步阻塞,防止主进程先于子协程结束
var wg sync.WaitGroup wg.Add(2) for i := 0; i < 2; i++ { go func(j int) { defer wg.Done() fmt.Println(j) }() } wg.Wait()
依赖管理
- 历史:GOPATH->Go Vendor->Go Moudle
- GOPATH:所有的依赖代码放在src下,存在不同项目的依赖冲突
- Go Vendor:在各自的项目文件下存放依赖包副本,避免各项目的依赖冲突
- Go Module:解决Go Vendor的间接依赖冲突问题,实现了依赖的版本控制。 选择最低兼容性版本解决冲突
- GOPROXY:解决依赖分发。使用逗号分隔各服务站点
单元测试
-
单元测试
-
命名:测试函数_test.go
func TestFuncName(t *testing.T) { testing statement } -
go test -v开启测试
go test -v --cover开启测试并且输出测试覆盖率
- 在test文件中推荐使用assert断言进行测试比较
-
-
Mock测试
- 测试需要保证幂等和稳定,常规的以文件为输入的测试难以保证这一点,为此可以使用Mock打桩测试,该测试的原理是使用了go中unsafe包,对动态运行的函数地址进行修改,替换输入测试函数为Patch函数。注意使用defer Unpatch()解除打桩测试
- 测试需要保证幂等和稳定,常规的以文件为输入的测试难以保证这一点,为此可以使用Mock打桩测试,该测试的原理是使用了go中unsafe包,对动态运行的函数地址进行修改,替换输入测试函数为Patch函数。注意使用defer Unpatch()解除打桩测试
-
基准测试
-
基准测试常常是用于对性能消耗的测试
func BenchmarkSelect(b *testing.B) { InitServerIndex() b.ResetTimer() for i := 0; i < b.N; i++ { Select() } } func BenchmarkSelectParallel(b *testing.B) { InitServerIndex() b.ResetTimer() b.RunParallel(func (pb *testing.PB) { for pb.Next() { Select() } }) }
-
项目实战
-
需求分析:监听http响应,返回对应请求所代表的帖子中的内容和回复 编码分析:
- 实体:用户、帖子、回复
- 服务:查看帖子(根据帖子id加载帖子内容和回复内容)、回复帖子(添加记录到数据库,绑定回复的帖子ID)
-
项目代码:回帖交互服务器
-
项目结构分析:
-
repository层:存放了数据库连接和初始化的函数、各Entity和对应的DAO(类似pojo和dao的结合)
-
post.go中包含了post的结构体定义和postdao的定义和相应dao方法的实现 其中值得注意的是:实例化DAO时使用了sync.Once,是单例模式的编程思想的体现
var postDao *PostDao var postOnce sync.Once func NewPostDaoInstance() *PostDao { postOnce.Do( func() { postDao = &PostDao{} }) return postDao } -
其他topic.go和user.go的实现类似
-
-
service层:上述查看帖子和回复帖子的服务实现
-
publish_post.go中的核心就是调用了post.go中的DAO方法,实现了新回复的持久化存储
func (f *PublishPostFlow) publish() error { post := &repository.Post{ ParentId: f.topicId, UserId: f.userId, Content: f.content, CreateTime: time.Now(), } if err := repository.NewPostDaoInstance().CreatePost(post); err != nil { return err } f.postId = post.Id return nil } -
query_page_info.go中首先实现了
userMap map[int64]*repository.User用户的内存索引。在页面内容用获取时使用了协程实现了性能优化
go func() { defer wg.Done() topic, err := repository.NewTopicDaoInstance().QueryTopicById(f.topicId) if err != nil { topicErr = err return } f.topic = topic }() //获取post列表 go func() { defer wg.Done() posts, err := repository.NewPostDaoInstance().QueryPostByParentId(f.topicId) if err != nil { postErr = err return } f.posts = posts }()
-
-
handler层:类似mvc中controller层和view层的结合,用于调用service中方法和向前端返回json化数据
-
handler层的内容就是简单的service调用和状态码及json内容的处理传递
topicId, err := strconv.ParseInt(topicIdStr, 10, 64) if err != nil { return &PageData{ Code: -1, Msg: err.Error(), } } //获取service层结果 pageInfo, err := service.QueryPageInfo(topicId) if err != nil { return &PageData{ Code: -1, Msg: err.Error(), } } return &PageData{ Code: 0, Msg: "success", Data: pageInfo, }
-
-
引用参考
Moonlight-Zhao/go-project-example (github.com)
Go 语言入门 - 工程实践 .pptx - 飞书云文档 (feishu.cn)