Go语言进阶与依赖管理 | 青训营笔记

72 阅读5分钟

这是我参与「第五届青训营 」笔记创作活动的第2天

Go语言进阶

并发编程

  1. 并发与并行

     并发:多线程程序在单核cpu上运行,通过时间片切换实现同时运行的状态。
     并行:多线程程序是在多核cpu上运行,通过多核实现多个线程的同时运行。
    
  2. 线程与协程

     线程:是一种内核态,是系统中一种比较重要的资源。它的各种操作都属于很重的系统操作,比较消耗资源。一个线程可以同时运行多个协程。栈内存大概在MB级别。
     协程:是一种用户态,是一种轻量级的线程。它的各种操作较线程而言轻量很多。栈内存大概在KB级别。
    
  3. 协程创建

     协程使用go关键字进行创建,创建格式为:`go 函数`。函数可以使用普通函数也可以使用匿名函数。
     
     示例如下:
         //匿名函数形式
         go func(paramlist1){
             //内容
         }(paramlist2)
         //普通函数形式
         go f(paramlist)
    
  4. 协程通信

     通过通信共享内存:使用通道连接协程,是一种特殊机制。
     通过共享内存实现通信:使用共享内存进行数据交换,此时需要使用互斥锁,影响通信的性能。
     
    
  5. Channel(通道)

     使用`make`函数进行创建,格式有两种,分别为:无缓冲和有缓冲。channel是并发安全的。
     创建示例如下:
     //无缓冲格式
     a := make(chan int)
     //有缓冲格式
     b := make(chan int, 5)
    
  6. Lock(锁)

     操作共享内存时,为保证并发安全需要使用锁。使用方式较为自由,但是必须有两个步骤,一个是使用Lock函数进行锁定操作,一个是使用Unlock函数进行解锁操作。
     
     示例如下:
         var (
             //共享内存
             x int32
             //定义锁
             lock sync.Mutex
         )
         //锁定
         lock.Lock()
         x++
         //解锁
         lock.Unlock()
    
  7. 并发的同步

     在Go语言中通过WaitGroup进行并发的同步。本质就是维护了一个计数器,在初始时调用`Add`方法指定一个协程数,在协程完成后调用`Done`方法进行减一操作。在同步的协程中使用`Wait`方法进行等待,直到减为0时,继续执行。
    
     示例如下
         //定义WaitGroup
         var wg sync.WaitGroup
         //指定协程数
         wg.Add(5)
         for i := 0; i < 5; i++ {
             go func(j int) {
                 //协程执行完毕,减一
                 defer wg.Done()
                 println("hello:" + fmt.Sprint(j))
             }(i)
         }
         //等待
         wg.Wait()
    

依赖管理

  1. 依赖管理演进

     GOPATH -> Go Vendor -> Go Module
     目前Go Module广泛使用。
    
  2. GOPATH

     `GOPATH`是Go语言的一个环境变量。有三个文件夹,分别为:`bin``pkg``src`。
     
     bin: 包含项目编译后的二进制文件
     pkg: 包含项目编译的中介产物,加速编译
     src: 包含项目的源码
     
     项目的所有依赖均存放于`src`中。
     
     弊端:无法实现package的多版本控制。
    
  3. Go Vendor

     `Go Vendor`就是在项目目录下增加vendor目录。
     
     vendor: 所有依赖的副本都放在该目录下。
     
     解决了package依赖的冲突问题。
     
     弊端:拉取的第三方包默认是最新版本,无法控制依赖的版本。
    
  4. Go Module

         `Go Module`通过`go.mod`文件来管理依赖包版本。通过go get/go mod指令工具管理依赖包。go get自1.17版本开始被弃用,可以使用go install指令。
         
         `Go Module`有三要素,分别为:配置文件、中心仓库、本地工具。
         
         配置文件(go.mod)    : 描述依赖,包括版本号、标识符等
         中心仓库(Proxy)     : 管理依赖,缓存第三方仓库中拉取的依赖,保证依赖的稳定,并减轻第三方压力
         本地工具(go get/mod): 对项目依赖进行管理,如下载、更新、删除等
    

测试

  1. 单元测试

     将测试数据输入到测试单元中得出输出,并与期望进行校对。测试单元包括模块、接口、函数等。
     
     规则:
         1. 所有测试文件都以`_test.go`结尾
         2. 测试函数命名规范`func TestXxx(*testing.T)`
         3. 初始化逻辑放到`TestMain`中
     
     单元测试的写法:
         1. 使用testing.T中的方法,如:`t.Errorf(错误信息)`
         2. 使用第三方的包,如:使用`github.com/stretchr/testify/assert`中的方法,`assert.Equal(t, 期望输出, 实际输出)`
     
     测试评估方法:
         代码覆盖率:测试中执行代码占实际代码的比例。
         获取方法:在命令行中使用命令 `go test 包 --cover`或者`go test 源文件 测试文件 --cover`
         提升覆盖率方法:对多分支进行测试,以覆盖更多的范围,提高测试的完备性。
    
  2. Mock

     使用第三方提供的支持来替换原始的普通函数,让测试环境不依赖于本地的文件,即解除外部依赖,从而保证测试的稳定。
     此处使用`github.com/bouk/monkey`中的`Patch(原方法,替换方法)`函数进行替换和`Unpatch(原方法)`函数解除替换。
    
  3. 基准测试

     用于性能分析
    
     规则:
         测试函数命名规范`func BenchmarkXxx(*testing.B)`
     写法:
         1. 串行测试
             for i := 0; i < b.N; i++ {
                 目标函数
             }
         2. 并行测试
             b.RunParallel(func(pb *testing.PB) {
                 for pb.Next() {
                     目标函数
                 }
             })
    

Go项目实战

本次项目为,实现发布主题与主题帖子和回显的功能。此处仅介绍开发大体流程:
1. 使用三层架构:即数据层、业务层、视图层
2. 此项目使用goin的web框架
3. 按照业务逻辑进行编码

个人总结

本次学习中,接触到了Go语言独有的并发编程机制:协程。协程具有很多优秀的性质,十分值得学习。
同时还接触到了Go语言的单元测试和基准测试。而对实战的接触也了解了一些Go语言开发项目的结构。

引用

  • 字节内部课:Go 语言入门 - 工程实践