Go语法及实战笔记2 | 豆包MarsCode AI刷题

132 阅读3分钟

Go并发编程

前置知识

并发

多线程程序在一个核的cpu上运行

并行

多线程程序在多个核的cpu上同时运行

Goroutine Go实现并发的工具

协程是用户态的轻量级线程,所用栈为KB级别 线程是内核态的,一个线程可以跑多个协程,所用栈为MB级别 具体实现已经在上一篇中有所设计

协程间的通信 Channel

通过通信共享内存

make(chan 元素类型, [缓冲大小]) 使用实例

func calSquare() {
    src := make(chan int)
    dest := make(chan int,2)
    go func() {
        defer close(src)
        for i := 0;i < 10; i += 2 {
            src <- i
    }
    }()
    go func() {
        defer close(dest)
        for i := range(src) {
            dest <- i * i
    }
    }()
    for i := range(dest) {
        fmt.println(i)
    }
}

并发安全

var(
    x int 
    lock sync.Mutex
    )
func calWithLock() {
    for i :=0; i < 2000; i++ {
        lock.Lock()
        x += 1
        lock.Unlock()
    }
}
func calWithoutLock() {
    for i :=0; i < 2000; i++ {
        x += 1
    }
}
func test() {
    x = 0
    for i := 0; i < 5;i++ {
        go calWithLock()
        }
    time.Sleep(time.Second)
    fmt.Println("cal with lock: ",x)
    x = 0
        for i := 0; i < 5;i++ {
        go calWithoutLock()
        }
    time.Sleep(time.Second)
    fmt.Println("cal without lock: ",x)
}

并发同步 WaitGroup

WaitGroup中存在有三个方法来实现对并发任务的计数 Add(delta int)Done()Wait()

func ManyGoWait() {
    var wg sync.WaitGroup
    wg.Add(delta 5)
    for i := 0;i < 5;i++ {
        go func(j int) {
            defer wg.Done()
            fmt.Println(j)
            }
        }
    wg.Wait()
}

依赖管理

Go 依赖管理演进

GOPATH -> GO Vendor -> Go Module

GOPATH

即为Go的环境变量,编译所需的源文件和编译结果都会保存在这个目录下。使用go get获取的包也会存在子目录/src下。

存在的问题,由于在不同的项目中可能需要用到不同版本的包,使用同一环境就会带来版本管理的困难。

Go Vendor

在项目目录下新增文件夹./vendor。所有依赖包的副本都会保存在这个目录下。程序在编译执行的时候也会优先在这个文件夹里搜寻所用到的包。

存在的问题 实际上是依赖于包的源码,无法控制依赖的版本。更新项目可能出现依赖冲突。

Go Module

定义版本规则和管理项目依赖关系 通过go.mod管理依赖版本

go mod init //创建go.mod
go mod download //下载所需依赖
go mod tidy //对依赖进行整理,下载需要的依赖,删除不要的依赖

通过go get/go mod指令工具管理依赖包

依赖分发-Proxy

为了避免直接从代码平台抓取依赖,同时保证所需依赖的长期稳定可用。因此引入了Proxy。Proxy会缓存所用过的依赖,包括各种版本。类似于加缓存的方法。 配置:

GOPROXY="https://proxy1.cn, https://proxy2.cn, direct"

测试

回归测试

集集成成测测试试

单单单元元元测测测试试试

从上到下,覆盖面逐渐提升,同时成本也随之降低。

单元测试

执行方式go test [flag][package]

-cover 查看覆盖率,对测试用例覆盖代码的情况进行检查

-v 显示每个用例的测试结果

-run TestFuncName 运行某一个用例

./example/
    |--func.go
    |--func_test.go
// ./example/func.go
package attention

func HelloTom() string {
    return "Jerry"
}

// ./example/func_test.go
package attention

import {
	"testing"
	"github.com/stretchr/testify/assert"
}
func TestHelloTom(t *testing.T) {
    output := HelloTom()
    expectedOutput := "Tom!"
    
	// 手动写判断语句进行判断
    if output!= expectedOutput {
        t.Errorf("Expected '%s', but got '%s'", expectedOutput, output)
    }
	调用assert包进行判断
	assert.Equal(t,expectedOutput,output)
}

注意事项

测试单元粒度足够小,函数单一职责 测试分支相互独立、全面覆盖 一般覆盖率在50%~60%

Mock测试

在测试涉及到外部依赖时,可以通过Mock测试模拟外部依赖完整的情况下进行测试,可以降低对测试环境的要求。

基准测试

对程序的执行效率进行测试

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()
        }
    })
}

import (
    "math/rand"
)

var ServerIndex [10]int

func InitServerIndex() {
    for i := 0; i < 10; i++ {
        ServerIndex[i] = i + 100
    }
}
//rand 函数的并发性能问题
//在并行执行的时候,为保证线程安全,rand函数会有一个全局锁,导致并行效率下降。
func Select() int {
    return ServerIndex[rand.Intn(10)]
}
//字节开源的快速随机的函数实现
//https://github.com/bytedance/gopkg
func FastSelect() int { 
    return ServerIndex[fastrand.Intn(10)]
}