这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
本节课程主要分为四个方面:
- 并发编程
- 依赖管理
- 单元测试
- 项目实战
并发编程
协程Goroutine
Goroutine: 是一种轻量级线程——协程。
1)相对线程,协程进行上下文切换的代价非常的小。
2)由于其上下文切换在用户态下发生,根本不必进入内核态,所以速度很快。而且只有当前goroutine 的 PC, SP等少量信息需要保存。
3)在Go语言中,每一个并发的执行单元为一个goroutine。
使用上也十分容易,相比c++ 多线程,多进程,线程池这些更容易理解也更好操作。只需要在调用函数前增加一个go就开启了一个协程。
在 main 中或者其下调用的代码中才可以使用 go + func() 的方法来启动协程。
main 的地位相当于主线程,当 main 函数执行完成后,这个线程也就终结了,意味着其开辟的协程无论是否终结都会自动终止。
如果想要执行,则需要对主线程休眠,确保协程能够完成返回。
通道Channel
可以认为一个管道连接多个go routine程序进行数据传递。 这种通信会让我们想起共享内存 但是这两者有着很大的不同。
共享内存是指多个线程或进程在内存中传递消息。而通道更像一个队列,遵循先入先出这样的规则。而且通道只能传递一种数据类型的数据。
channel 又分为无缓冲通道和有缓冲通道。二者的区别在于开辟的空间大小。
pipeline := make(chan int)
pipeline := make(chan int,3)
下面就是一个计算平方数的例子,很好的表明channel的使用方法以及它是如何传递的。
线程同步WaitGroup
之前提到过主线程完成后会终止内部所有协程的运行,为了保证协程能够运行结束我们增加了sleep来确保协程能够运行结束。但在实际开发中,开发人员是无法预知所有的 goroutine 需要多长的时间才能执行完毕,sleep 多了主程序就阻塞了, sleep 少了有的子协程的任务就没法完成。
WaitGroup 能够很好解决这一问题
var 实例名 sync.WaitGroup
实例化完成后,就可以使用它的几个方法:
Add:初始值为0,你传入的值会往计数器上加,这里直接传入你子协程的数量Done:当某个子协程完成后,可调用此方法,会从计数器上减一,通常可以使用 defer 来调用。Wait:阻塞当前协程,直到实例里的计数器归零。
依赖管理
Gopath
项目代码直接依赖src下的源码,利用 go get 下载最新的包到src目录下
缺点:无法满足多版本控制,只能保存一个版本的代码
Go Vendor
在目录增加了vector 文件 保存每个版本的依赖副本
缺点:无法解决同一个包不同版本的问题,可能存在不兼容问题导致依赖冲突
Go Module : go.dev/blog/using-…
通过go.mod 文件管理依赖包的版本
能够保存版本信息,依赖关系,兼容关系
go proxy
作为一个缓存站点,对软件内容进行缓存,确保go module 每次访问都能获取
go proxy 是一个url列表 可以指定源站进行寻找依赖
单元测试
单元测试概念和规则:示例
- 所有的测试文件以_test.go 结尾
- func TestXxx(*testing.T)
- 初始化逻辑放在TestMain中
- 可使用 go test 运行
Mock测试:github.com/bouk/monkey
单元测试需要保证稳定性和等幂性,mock机制能够很好的保障这一点
以monkey 库为例,monkey patch 的作用域在runtime。能够将内存中函数地址替换为运行时的函数地址,将待打桩函数或者方法进行跳转
基准测试:pkg.go.dev/testing#hdr…
基准测试可以反复的运行函数,从而建立基准,并且无须执行运行次数,因为框架会通过调整次数来获得可靠的数据集,基准测试结束后将获得一个报告,包含了运行次数以及运行一次消耗的时间,单位为 ns。
基准测试函数的命名方式为 BenchmarkXxx 并且要求传入一个 *testing.B 类型。