Go语言进阶与开发基础|青训营笔记

134 阅读1分钟

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

一、Go语言进阶(并发编程)

  1. 并发与并行

    • 并发:多线程程序在cpu的一个核上运行(通过切换时间片的方法实现)
    • 并行:多线程程序在cpu的多个核上运行
  2. Goroutine

    • 线程:内核态,栈为mb级
    • 协程:用户态,一个线程上运行多个协程,栈为kb级(无法利用多核资源,本质上是一个线程,即不可实现并行)

    在Golang中可使用关键字go开启一个goroutine,例子如下:

    func GoRoutine() {
        for i := 0; i < 5; i++ {
            go func(j int) {
                fmt.Println(j)
            }(i)
        } 
    }
    
  3. CSP(communicating sequential processes)

    不同的线程/协程之间需要通信,有两种不同的方法:

    • 通过通信共享内存(go中推荐):通过通道传输数据实现通信
    • 通过共享内存实现通信:在共享的内存中记录需要通信的内容,为了避免多个线程/协程同时修改一块内存,一般需要进行加锁的操来保证正确
  4. Channel

    在Golang中创建Channel:

    make(chan 元素类型, 缓冲大小)

    // 无缓冲
    chan1 := make(chan int)
    // 有缓冲,大小为3
    chan2 := make(chan int, 3)
    

    image-20220508160449928.png

    • 无缓冲通道:当一个Gorountine向channel中发送一个元素后,channel会堵塞(即其他向该channel发送元素的操作会进入等待),直到channel中的元素被一个Gorountine读走
    • 有缓冲通道:当一个Gorountine向channel中发送一个元素后,channel并不会直接堵塞,而是可以在元素尚未被读走的情况下继续向其中发送元素,直到channel内剩余元素个数达到缓冲大小限制才堵塞,这种方法适用于一些特殊情况,比如发送端的发送速度高于接收端的接收速度时
  5. 并发安全Lock

    Channel是通过通信共享内存,是Golang中的推荐做法,但Golang同样可以通过共享内存实现通信

    // 定义一个锁
    var lock sync.Mutex
    // 定义一个全局变量
    x := 0
    ​
    func add() {
        for i := 0; i < 2000; i++ {
            lock.Lock()
            x += 1
            lock.Unlock() 
        }
    }
    ​
    func main() {
        for i := 0; i < 5; i++ {
            go add()
        }
    }
    
  6. WaitGroup

    不同Gorountine的执行速度并不一样,如果外部主协程过早退出会导致Gorountine没有执行完就退出。我们可以使用

    time.Sleep(time.Second)
    

    来手动延时来确保所有Gorountine都结束后再退出主协程,但绝大部分时候并不能准确预估Gorountine需要的执行时间,如果延时过多会极其影响使用体验

    使用WaitGroup可以很优雅地解决这个问题,WaitGroup实际上是在内存中维护了一个计数器,具体使用如下:

    func main() {
        // 定义一个WaitGroup
        var wg sync.WaitGroup
        for i := 0; i < 5; i++ {
            // 计数器+1
            wg.add(1)
            go func() {
                // 一个Gorountine已完成,计数器-1
                defer wg.Done()
                /*
                operation
                */
            }()
        }
        // 等待,直到计数器归零
        wg.Wait()
    }
    

二、依赖管理

Golang的依赖管理有GOPATH、Go Vendor和Go Module三种,Go Module使用最多,因此只对Go Module作详细介绍

  1. Go Module:

    • go.mod文件管理依赖包版本
    • go get/go mod指令工具管理依赖包
  2. 依赖管理三要素

    • 配置文件描述依赖:go.mod
    • 中心仓库管理依赖库:Proxy
    • 本地工具:go get/mod
  3. 工具-go mod

    go mod init 初始化,创建go.mod文件

    go download 下载模块到本地缓存

    go mod tidy 自动增加需要的依赖,删除不需要的依赖(最常用)

三、测试

  1. 单元测试

    • 测试文件

      测试文件以_test.go结尾

    • 测试函数

      func TestXxx(*testing.T){}

      指令: go test[flags] [packages]

    • 初始化逻辑放到TestMain中

      func TestMain(m *testing.M) {}

    • 通过assert.Equal()得到测试的覆盖率

      使用指令:go test Xxx_test.go Xxx.go --cover

  2. Mock测试

    • 使用开源项目github.com/bouk/monkey
    • 快速Mock函数可以为一个函数或者方法打桩,不依赖本地文件
  3. 基准测试

    • 测试函数

      func BenchmarkXxx(b *testing.B)

    • 使用指令 go test -bench=.