Go在工程中的实践 | 青训营笔记

73 阅读2分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记

在实际的项目工程中,尤其是目前阶段的项目都离不开高并发,高性能,多版本的快速迭代。这就要求我们的项目有非常好的鲁棒性。下面从Go语言的并发,测试,依赖管理简单说说Go在实际工程项目中的实践:

并发编程

  • 协程和线程:

    Go可以充分的发挥多核优势,高效运行。其协程的引入是比线程更加轻量级的执行单位。协程为用户态,栈大小为KB级别;线程为内核态,栈大小为MB级别。所以一个线程在理想情况下开多个协程都是可以的。协程的用户态决定了程序在执行的过程中协程之间的切换并不需要下潜到内核,所以,减少了不必要的开销,运行速度很快。

  • 协程间的通信:

    我们所熟悉的Linux操作系统,常见的进程间的通信方式有:有名管道FIFO,无名管道pipe,共享内存,内存映射,本地socket等。而协程间的通信为channel方法,当然也保留了传统的通信方式。官方建议使用前者,后者会有数据竟态的情况出现,所以引出了并发安全问题 => 锁。

    • channel:

      • make(chan 元素类型,[缓冲大小])
      • 无缓冲通道 make(chan int)
      • 有缓冲通道 make(chan int, 2)
      func CalSquare() {
          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:

    • Go语言提供了sync包,其中提供了基本的同步基元,比如互斥锁,读写锁,条件变量等,这些都是适用于低水平的程序线程,比较简单。在复杂的代码中我们WaitGroup和Once,以及上面的channel则会更好一些

    • 互斥锁,读写锁,条件变量和Linux中的三者大同小异,这里不在一一赘述

    • WaitGroup:

      • 本质是内部维护一个计数器

      • 提供三个方法:Add(int),Done(),Wait(),分别对计数器进行加一,减一,回收操作

      • wait()函数等待,当计数器为0时,阻塞结束,回收资源

        var wg sync.WaitGroup
        var urls = []string{
            "http://www.golang.org/",
            "http://www.google.com/",
            "http://www.somestupidname.com/",
        }
        for _, url := range urls {
            // Increment the WaitGroup counter.
            wg.Add(1)
            // Launch a goroutine to fetch the URL.
            go func(url string) {
                // Decrement the counter when the goroutine completes.
                defer wg.Done()
                // Fetch the URL.
                http.Get(url)
            }(url)
        }
        // Wait for all HTTP fetches to complete.
        wg.Wait()
        
    • 详情可以参考官方文档:sync

依赖管理

  • Go语言的依赖管理的发展路线为:GOPATH --> Go Vendor --> Go Module
  • 之所以最终使用Go Module是因为前两者都会出现无法控制版本的问题,因为他们都是依赖于源代码,并没有一个对版本的概念
  • 依赖的三要素:配置文件go.mod,依赖库Proxy,本地工具go get / mod
  • Go语言在编译的时候优先选择最低的兼容版本
  • 配置文件go.mod:

    • eg:image-20220508110743913.png
    • version字段:image-20220508110838268.png
    • indirect字段:标识是非直接依赖(比如A依赖于B,B依赖于C,则AC之间就是非直接依赖),起到一个标识的作用
    • incompatible字段:对于主版本在大于2的时候要在模块的路径增加 "/vN"后缀,对于没有go.mod文件并且主版本大于2,则会用incompatible字段标识出来
  • Proxy:

    • 作用:缓冲第三方版本管理的压力,保证稳定性image-20220508112158608.png
    • 变量GOPROXY:image-20220508112335453.png
  • go get / mod: image-20220508112520162.png

image-20220508112552776.png

单元测试

  • 规则:

    • 所有测试文件以_test.go结尾
    • func TestXxx(*testing.T)
    • 初始化逻辑放在TestMain中
  • assert包帮助我们比较

  • 覆盖率:--cover参数

  • image-20220508113218193.png

  • image-20220508114456728.png

  • Mock测试

    • 为一个函数打桩
    • 为一个方法打桩