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

127 阅读3分钟

pi.jpg

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


一、并发编程

  • 协程Goroutine:用户态,轻量级线程,栈KB级别,只需在用户态即可完成上下文的切换,使用go关键字即可开启线程
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)  
}
  • 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:加锁确保原子性,定义一个变量类型为sync.Mutex的锁,在需要使用锁的位置前后分别添加Lock()Unlock()
var (
    x  int64
    lock sync.Mutex
)

func addWithLock() {
    for i := 0; i < 2000; i++ {
        lock.Lock()
        x += 1
        lock.Unlock()
    }
}
  • 线程同步WaitGroup:WaitGroup等待goroutine的收集完成。主要的goroutine调用Add来设置要等待的goroutine数量。然后运行每个goroutine,并在完成时调用Done。同时,可以使用Wait来阻塞,直到所有goroutine都完成
func ManyGoWait() {
    var wg sync.WaitGroup
    wq.Add(5)
    for i := 0; i < 5; i++ {
        go func(j int) {
            defer wg.Done
            hello(j)  //输出是无序的
        }(i)
    }
    wg.Wait() 
}

二、依赖管理

依赖管理三要素

  1. 配置文件,描述依赖 go.mod
  2. 中心仓库管理依赖库 Proxy
  3. 本地工具 go get/mod
  • Go Module
    • 通过go.mod文件管理依赖包版本
    • 通过go.get/go mod指令工具管理依赖包
  • 依赖配置-version
    • 语义化版本: V1.3.0 --大版本.新增版本前后兼容.bug修复
    • 基于commit伪版本:vx.0.0-20020506112233-a4g6a -- 语义-时间戳-随机哈希值
  • 依赖配置-indirect
    • 标识没有直接导入的依赖模块
  • 依赖配置-incompatible
    • 可能会存在不兼容的代码逻辑
  • 依赖分发-变量GOPROXY
  • 工具-go mod
    • init ,初始化,创建go.mod文件
    • download ,下载模块到本地缓存
    • tidy ,增加需要的依赖,删除不需要的依赖

三、测试

单元测试

  1. 规则:所有测试文件以 _test.go 结尾
  2. func TestXxx(*testing.T)
  3. 初始化逻辑放到 TestMain 中
  • assert:通过判断预期值与输出值是否相等,来确定是否出错
import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestJudgePassLineTrue(t *testing.T) {
	isPass := JudgePassLine(70)
	assert.Equal(t, true, isPass)
}
  • 覆盖率:go test Xxx_test.go Xxx.go --cover
  • 技巧:测试分支相互独立、全面覆盖;测试粒度足够小,函数单一职责
  • 依赖:幂等(多次运行结果不变)、稳定(独立运行)

MOCK测试

monkey: github.com/bouk/monkey

用一个函数替代另一个函数,实现打桩,减少依赖

monkey.Patch 打桩

defer monkey.Unpatch 对桩进行卸载

import (
	"bou.ke/monkey"
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestProcessFirstLine(t *testing.T) {
	firstLine := ProcessFirstLine()
	assert.Equal(t, "line00", firstLine)
}

func TestProcessFirstLineWithMock(t *testing.T) {
	monkey.Patch(ReadFirstLine, func() string {
		return "line110"
	})
	defer monkey.Unpatch(ReadFirstLine)
	line := ProcessFirstLine()
	assert.Equal(t, "line000", line)
}

基准测试

测试一段程序运行时的性能和CPU损耗

  • 以前缀Benchmark标识一个基准测试
func BenchmarkXxx(*testing.B)
  • 使用go test Xxx.go -bench运行一个基准测试

  • 基准测试函数必须运行目标代码 b.N 次。 在基准测试执行期间,b.N 被调整,直到基准测试函数持续 足够长,可以可靠地计时

func BenchmarkRandInt(b *testing.B) {
    for i := 0; i < b.N; i++ {
        rand.Int()
    }
}

b.ResetTimer() :如果基准测试在运行前需要一些昂贵的设置,计时器可以重置

b.Next():下一步报告是否有更多迭代要执行