go进阶学习 | 青训营笔记

130 阅读3分钟

1 课程资料

导学【Go 语言原理与实践学习资料】第三届字节跳动青训营-后端专场 - 掘金

项目GitHub - Moonlight-Zhao/go-project-example at V0

PPT: Go语言入门-工程实践

2 并发

2.1 Goroutine

使用 go f(s) 在一个协程中调用这个函数。 协程并发执行。

package concurrence
​
import (
    "fmt"
    "time"
)
​
func hello(i int) {
    println("hello goroutine : " + fmt.Sprint(i))
}
​
func HelloGoRoutine() {
    for i := 0; i < 5; i++ {
        go func(j int) {
            hello(j)
        }(i)
    }
    time.Sleep(time.Second)
}
​

这里启动了5个协程并发调用hello函数。

使用WaitGroup来阻塞主协程,可以等待所有子协程执行完。常用方法如下:

方法名功能
(wg * WaitGroup) Add(delta int)等待组的计数器 +1
(wg * WaitGroup) Done()等待组的计数器 -1
(wg * WaitGroup) Wait()当等待组计数器不等于 0 时阻塞直到变 0。
package concurrence
​
import (
    "fmt"
    "time"
    "sync"
)
​
func hello(i int) {
    println("hello goroutine : " + fmt.Sprint(i))
}
​
func HelloGoRoutine() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(j int) {
            defer wg.Done()
            hello(j)
        }(i)
    }
    wg.Wait()
    //time.Sleep(time.Second)
}
​

2.2 CSP并发模型

image-20220516135912892.png

2.3 Channel

使用 make(chan val-type) 创建一个新的通道

无缓冲(默认):只有对应的接收(<- chan) 通道准备好接收时,才允许进行发送(chan <-)。

有缓冲:允许在没有对应接收者的情况下,缓存一定数量的值

package concurrence
​
func CalSquare() {
    src := make(chan int) //无缓冲
    dest := make(chan int, 3) //有缓冲
    go func() {
        defer close(src)
        for i := 0; i < 10; i++ {
            //使用 channel <- 语法 发送 一个新的值到通道中
            //使用 <-channel 语法从通道中 接收 一个值
            src <- i
        }
    }()
    go func() {
        defer close(dest)
        for i := range src {
            dest <- i * i
        }
    }()
    for i := range dest {
        //复杂操作
        println(i)
    }
}
​

2.4 锁

锁和其他语言的用法类似,这里就不赘述了

var (
    x    int64
    lock sync.Mutex     // 定义锁
)
​
func addWithLock() {
    for i := 0; i < 2000; i++ {
        lock.Lock()     // 加锁
        x += 1
        lock.Unlock()   // 解锁
    
}

3 依赖管理

3.1 go mod

modules是源代码交换和版本控制的单元.

默认下,以下情况启用module功能。这

  • 当前目录在GOPATH/src之外且该目录包含go.mod文件
  • 当前文件在包含go.mod文件的目录下面

使用go mod init 初始化生成go.mod 文件,执行 go run server.go 运行代码会发现 go mod 会自动查找依赖自动下载,包的版本需要升级可以使用go get系列语句。go mod tidy清理未使用的module,下载所需的包,go list -m all列出当前module和所有依赖。

3.2 依赖分发

image-20220516143852179.png Go Modules系统中定义的依赖,最终可以对应到多版本代码管理系统中某一项目的特定提交或版本,对于go.mod中定义的依赖,则直接可以从对应仓库中下载指定软件依赖,从而完成依赖分发。但直接使用版本管理仓库下载依赖,存在多个问题。

go proxy应运而生,Go Proxy是一个服务站点,它会缓源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用,从而实现了提供"immutability"和"available"的依赖分发;使用Go Proxy之后,构建时会直接从Go Proxy站点拉取依赖。

Go Modules通过GOPROXY环境变量控制如何使用Go Proxy; GOPROXY是一个Go Proxy站点URL列表,可以使用"direct"表示源站。对于示例配置,整体的依赖寻址路径,会优先从proxy1下载依赖,如果proxy1不存在,后下钻proxy2寻找,如果proxy2,中不存在则会回源到源站直接下载依赖,缓存到proxy站点中。

Go proxy 设置链接分享:www.jianshu.com/p/99aa7522c…

4 测试

4.1 单元测试

Go 语言推荐测试文件和源代码文件放在一块,测试文件以 _test.go 结尾

example/
   |--calc.go
   |--calc_test.go

测试函数格式为:”func TestXxx(t *testing.T)“

package concurrence
​
import "testing"func TestCalSquare(t *testing.T) {
    CalSquare()
}
​

进行测试之前需要初始化操作(例如打开连接),测试结束后,需要做清理工作(例如关闭连接)等等。这个时候就可以使用TestMain()。

4.2 mock测试

如果文件被修改或者删除测试就会fail。为了保证测试case的稳定性,我们对读取文件函数进行mock,屏蔽对于文件的依赖。

monkey是一个Go单元测试中十分常用的打桩工具,指针赋值Mockey Patch的作用域在Runtime, 在运行时通过通过Go的unsafe包,能够将内存中函数的地址替换为运行时函数的地址。这样就可以不对内存中的文件有强依赖。

为一个函数打桩:用一个函数A去替换原函数B,A就是一个打桩函数。

安装

go get bou.ke/monkey
func TestProcessFirstLineWithMock(t *testing.T) {
    // 通过patch对Readfineline进行打桩mock,默认返回line110
    monkey.Patch(ReadFirstLine, func() string {
        return "line110"
    })
    // 通过defer卸载mock,这样整个测试函数就摆脱了本地文件的束缚和依赖。
    defer monkey.Unpatch(ReadFirstLine)
    line := ProcessFirstLine()
    assert.Equal(t, "line000", line)
}

4.3 基准测试

Go语言标准库内置的testing测试框架提供了基准测试(benchmark)的能力,有利我们进行代码的性能优化

基准测试以Benchmark开头,入参是testing.B,用b中的N值反复递增循环测试(对一个测试用例的默认测试时间是1秒,当测试用例函数返回时还不到1秒,那么testing.B中的N值将按1、2、5、10、20、50递增,并以递增后的值重新进行用例函数测试。)

Resttimer重置计时器,我们再reset之前做了init或其他的准备操作,这些操作不应该作为基准测试的范围;

runparallel是多协程并发测试;执行2个基准测试,发现代码在并发情况下存在劣化,主要原因是Select()函数中调用了rand,rand为了保证全局的随机性和并发安全,持有了一把全局锁。fastrand在高并发则会效率更高,具体可以自己搜索了解一下

package benchmark
​
import (
    "github.com/bytedance/gopkg/lang/fastrand"
    "math/rand"
)
​
var ServerIndex [10]intfunc InitServerIndex() {
    for i := 0; i < 10; i++ {
        ServerIndex[i] = i+100
    }
}
​
func Select() int {
    return ServerIndex[rand.Intn(10)]
}
​
func FastSelect() int {
    return ServerIndex[fastrand.Intn(10)]
}
package benchmark
​
import (
    "testing"
)
​
func BenchmarkSelect(b *testing.B) {
    InitServerIndex()
    b.ResetTimer()  // 重置定时器
    for i := 0; i < b.N; i++ {
        Select()
    }
}
​
func BenchmarkSelectParallel(b *testing.B) {
    InitServerIndex()
    b.ResetTimer()
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            Select()
        }
    })
}

image-20220516153953123.png

image-20220516153933111.png

参考资料

Go语言入门教程,Golang入门教程

Go by Example 中文版

go mod 使用