这是我参与「第五届青训营 」伴学笔记创作活动的第2天
一、主要课程内容
1、语言进阶:从并发编程的视角了解Go高性能的本质;
2、依赖管理:学习Go语言依赖管理的演进路线;
3、测试:从单元测试实践出发,学习提升质量意识;
4、项目实战:通过项目需求,项目拆解,逻辑设计,代码实现学习真实的项目开发。
二、详细知识记录
1、并行和并发:并行是在cpu的多核上真正同时进行多个线程的执行,而并发则是在一个cpu核心上通过划分时间片的方式进行多个线程的执行。广义的并发可以理解为系统对外的特征和能力。Go语言可以充分发挥多核调度优势。
2、线程和协程:线程是处于内核态,是系统比较昂贵的资源,创建切换和停止都要消耗大量资源;协程可以理解成轻量级的线程,处于用户态,通过语言自身进行调度。线程可以调用多个协程,goroutine是Go语言高并发的核心所在。在Go语言中创建协程的方式:只需要调用函数的时候在函数的前面加上go关键字。
3、协程之间的通信:Go语言提倡通过通信来共享内存而不是通过共享内存来通信,前者则涉及到一个重要概念channel,就像传输队列遵循先入先出以保证数据顺序,可以让一个goroutine发送特定的值到另一个goroutine的通信机制;而后者则是操作系统中通过共享内存(临界区),互斥量加锁来实现不同goroutine的通信,容易影响程序性能。
4、channel的具体操作:创建需要通过make关键字make(chan 元素类型, [缓冲区大小]),而根据缓冲区的大小,channel又可以分为无缓冲通道和有缓冲通道。前者可以使两个goroutine同步化,又称同步通道;解决同步问题的方式是使用带缓冲区的有缓冲通道,这也是典型的生产消费模型。带缓冲的channel可以解决生产和消费速度不均衡带来的执行效率问题。
5、并发安全lock:Go语言也保存着通过共享内存来实现通信的机制,这就可能存在同一时间多个goroutine操作同一内存资源的情况,要通过互斥量加锁的操作来避免。例子中给出的+2000的例子展示出如果不加锁就会得到一个未知量,这就是并发安全问题。
6、waitgroup:实现并发的同步,sleep不够优雅而且我们也无法判断具体协程的运行时间。waitgroup内部实际上维护了一个计时器,可以增加减少,ADD方法是记录并发任务的数量,Done方法是当任务处理完就-1,Wait方法可以阻塞直到计数器为零即所有任务执行完毕。
7、依赖管理:依赖指各种开发包,利用他人封装好的开发组件和工具。Go的依赖管理演进GOPATH、Go Vendor、Go Moudule。不同环境项目依赖的版本不同,要控制依赖库的版本。GOPATH是Go项目的工作区,bin是编译的二进制文件,pkg是编译的中间产物加速编译,src是项目源码。GOPATH的弊端在于所有依赖在pkg里面,如果两个project需要不同版本的pkg,则无法实现多版本控制;Go Vendor是在项目目录下添加一个vendor文件夹,所有依赖包副本形式放在文件夹中,优先在vender目录下获取pkg。Go Vender的弊端在于如果有一个project依赖B,C两个pkg,而B,C分别依赖D的两个不同版本,这时候也会发生依赖冲突导致编译出错,归根结底还是因为无法标识版本的概念;Go Moudule是官方推出的依赖管理系统,解决了上述问题。通过go.mod文件管理依赖包版本,通过go.get/ go.mod管理依赖包,实现了定义版本规则和管理项目依赖关系。
8、依赖管理的三要素:配置文件描述哪些依赖,中心仓库管理依赖库,本地工具。Go里面分别对应go.mod,proxy,go.get/ go.mod。其中go.mod的三部分组成。go.mod的标识版本规则两种类型。两种后缀的含义 indirc和incompatible。
上图在编译的时候选择的C库的版本为1.4(1.3和1.4相互兼容),Go语言会选择最低的兼容版本。
9、依赖分发:go.mod中定义的依赖最终都是对应到多版本代码仓库管理系统中的某项目的特定提交,所以go.mod的依赖就可以直接从对应仓库中下载到指定的依赖完成依赖分发。但是呢这样无法保证构建的稳定性和可用性,比如github作者更新或者删除了某一版本。解决这一问题就要用Proxy,会缓存原站中的软件内容,版本不会改变,保证了可靠性。
10、GOPROXY的配置:通过GOPROXY环境变量来控制proxy的配置。本质就是url列表,用逗号分隔。可以设置多层依次访问。
11、go get工具和go mod工具
12、测试分类:回归测试指成员手动通过终端回归一些固定主流场景,例如刷个抖音;集成测试指对系统的功能维度进行测试,例如某个接口的功能;单元测试指测试开发阶段对单独函数模块进行验证,从上到下覆盖率逐层变大,成本逐层降低。所以单元测试决定代码质量。
13、单元测试:规则需要以_test.go结尾,func TestXxx命名,初始化逻辑放到Testmain中。衡量代码是否经过足够测试,评估项目的测试水平使用覆盖率衡量(按代码行数计算)。一般覆盖率50-60;测试分支要相互独立,全面覆盖;测试单元粒度足够小,函数单一职责。单元测试的外部依赖要做到幂等和稳定————mock机制。通过类似替换函数的操作,实现不对本地文件的强依赖。
14、基准测试:Go内置的测试框架提供了基准测试的能力,主要对代码的性能进行测试。BenchnarkXxx命名,参数是testing.B。并行的用法。示例测试可以用fastrand去代替rand来提高并发的效率。
三、项目实战
项目的结构分层
代码设计
测试运行
四、学习感受
第二节课的内容很多,难度也有所提升,涉及到一些深层次的概念和较为大一些的项目的实现,需要花时间去消化一下。