概述
对应Go的并发,依赖管理,测试做说明。
语言进阶
从并发编程角度了解go高性能的本质。
协程
先解释几个概念
- 并发指的是多线程程序在一个核的cpu上运行,并发主要通过时间片切换来改变状态
- 并行指的是多线程程序在多个核的cpu上运行,并行主要是利用多核实现多线程运行
- 协程:用户态,轻量级线程,栈KB级别。
- 线程:内核态,线程跑多个协程,栈MB级别
go语言则可以充分发挥多核优势,高效运行,go语言还有一个很大优势就是协程,go语言一次可以处理上万个协程。 下面一个例子展示了协程的使用
func hello(i int) {
println("hello world : " + fmt.Sprint(i))
}
func ManyGo() {
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()
}
该ManyGo函数初始化一个 WaitGroup ( wg),然后运行一个从 0 到 4 的循环。在每次迭代中,它将 WaitGroup 计数器 ( wg.Add(1)) 加 1 并使用匿名函数启动一个新的 goroutine。匿名函数将循环变量作为i参数,并推迟调用wg.Done()以标记 goroutine 的结束。在这个 goroutine 中,它hello使用当前值 来调用函数i。
使用的目的sync.WaitGroup是确保ManyGo函数在返回之前等待所有生成的 goroutine 完成。这是通过调用 来完成的wg.Wait(),它会阻塞直到 WaitGroup 的计数器变为零(即所有 goroutine 都已调用wg.Done())。
下面是WaitGroup的几个方法
- -
Add(delta int):此方法从 WaitGroup 计数器中添加或减去一个值。 Done():此方法将 WaitGroup 计数器减 1。它通常在每个 goroutine 任务结束时调用,以表示该 goroutine 已完成其工作。Wait():该方法会阻塞程序的执行,直到 WaitGroup 计数器变为零。如果计数器已经为零,Wait则立即返回。否则,它会等待,直到所有 goroutine 调用Done()足够多的次数才能将计数器降至零。
通道
CSP用于描述和推理使用通过通道相互通信的进程的并发系统。
有两张方式
- 通过通道共享内存
- 通过共享内存实现通信。
在go语言中提倡通过通信共享内存,所以我们下面将go语言的通道:
-
无缓冲通道(Unbuffered Channel):
- 在创建时不指定中心尺寸,即
make(chan DataType)。 - 无缓冲通道的发送(
<-)和接收操作会导致发送方和接收方相互阻塞,直到两个goroutine准备好进行数据交换,这种交换是同步的。 - 无缓冲通道用于强制goroutine之间的同步,保证数据的安全交换和顺序执行。
- 在创建时不指定中心尺寸,即
ch := make(chan int) // 创建一个无缓冲通道
-
有缓冲通道(Buffered Channel):
- 在创建时指定中心尺寸,即
make(chan DataType, bufferSize)。 - 有缓冲通道的发送操作只有在通道开关为空时才会阻塞,而接收只有在通道开关为空时才会阻塞。
- 当通道弧度未满时,发送操作会立即返回,不需要等待接收方处理数据,这种交换是异步的。
- 有缓冲通道适用于解耦发送方和接收方的执行,提高性能,但需要注意控制灯光大小,避免潜在的内存问题。
- 在创建时指定中心尺寸,即
ch := make(chan string, 10) // 创建一个大小为10的有缓冲通道
通道的基本操作包括:
- 发送数据到通道:
channelName <- data - 从通道接收数据:
data := <-channelName - 关闭通道:
close(channelName)
注意事项:
- 向已关闭的通道发送数据会导致恐慌,但从已关闭的通道接收数据会在没有数据可接收时立即返回零值。
- 通道的发送和接收操作都是阻塞的,所以要保证在通道上有其他goroutine执行相应的操作,否则可能导致死锁。
- 对于无缓冲通道,发送和接收操作是同步的;对于有缓冲通道,发送操作是异步的。
注: Go语言中的通道是非常有用的工具,它们使并发编程更容易管理和安全。正确使用通道可以有效地避免竞态条件(Race Condition)和其他常见的并发问题。
Go依赖管理
跟java的maven一样,go需要一个依赖管理工具,在时代下Gopath,Govender被淘汰后,Go Module变成了当下使用最高的管理工具, Go Module 解决了这些问题,并提供了以下优势:
- 版本管理: Go Module 允许开发者明确指定项目所依赖的外部库的版本,以确保项目的稳定性和一致性。每个项目都有自己的 go.mod 文件,其中记录了所需的依赖库及其版本。
- 解决依赖: Go Module 可以自动解决和下载项目所需的依赖,无需手动复制文件到 GOPATH。
- 版本锁定: Go Module 可以锁定项目的依赖版本,保证每次构建都使用相同的依赖版本,避免出现意外的更新。
- 私有仓库支持: Go Module 支持从私有代码仓库(如 GitLab、Bitbucket 等)导入依赖。
使用 Go Module 的步骤如下:
- 确保 Go 的版本高于 1.11。
- 在项目目录下运行
go mod init命令,以创建或初始化 go.mod 文件。这个文件用来记录项目的依赖信息。 - 在代码中导入所需的包。Go Module 会自动检测并下载所需的依赖。
- 可以使用
go get命令来获取特定的包或更新依赖:go get package-name。 - 在项目构建或运行时,Go Module 会自动查找和使用 go.mod 文件中指定的依赖。
具体操作在下一篇会演示说明。
测试
在Go语言中,测试是一项重要的开发活动,它帮助开发者验证代码的正确性、稳定性和性能。Go语言内置了一个轻量级的测试框架,让编写和运行测试变得非常简单。
Go语言的测试框架使用testing包来创建和运行测试。以下是一些关于Go语言测试的重要知识点和示例:
- 测试函数命名: Go语言中的测试函数必须以
Test开头,例如TestMyFunction,这样测试工具才能识别并运行它们。 - 测试文件: 测试函数应该定义在以
_test.go为后缀的测试文件中。这些文件包含测试代码和函数,但不会被编译到最终的可执行程序中。 - 测试用例: 通常,测试函数会针对不同的输入数据编写多个测试用例。可以使用
t.Run来创建子测试用例,这样可以更好地组织测试输出。 - 测试辅助函数: 如果有一些通用的测试辅助函数,可以将它们放在测试文件中,命名类似于
func TestXxxHelper(t *testing.T)。 - 测试断言: 使用
testing包中的断言函数来验证测试结果,例如t.Errorf、t.Fatalf、t.Logf等。
在Go语言中,可以使用一些库来实现单元测试的模拟,其中最流行的是github.com/stretchr/testify/mock库。该库提供了一套功能强大且易于使用的模拟功能。
下面是一个使用github.com/stretchr/testify/mock库进行单元测试模拟
使用模拟库,我们可以方便地定义和控制函数的行为,确保单元测试的可控性和可重复性。这种方法允许我们专注于测试我们的代码逻辑,而不必担心依赖项的实现。
还有基准测试
基准测试(Benchmarking)是一种用于评估代码性能的测试方法。在Go语言中,基准测试由标准库testing提供支持。基准测试允许开发者对代码的不同实现进行性能比较,以便找出最优的实现方式。
基准测试的编写与普通单元测试相似,只是使用testing.B类型的参数来代替testing.T类型的参数。testing.B提供了一些额外的方法和计时器,用于测量和评估代码的性能。
要运行基准测试,我们可以使用go test命令,并加上-bench标志,后面跟上需要运行的基准测试函数名:
go test -bench=.
输出结果可能类似于:
BenchmarkConcatWithPlusOperator-8 1000000000 0.273 ns/op
BenchmarkConcatWithJoin-8 1000000000 0
BenchmarkConcatWithPlusOperator-8 1000000000 0.273 ns/op
BenchmarkConcatWithJoin-8 100000000
BenchmarkConcatWithPlusOperator-8 1000000000 0.273 ns/op
Benchmark
BenchmarkConcatWithPlusOperator-8 1000000000 0.273 ns/op
BenchmarkConcatWithPlusOperator-8 1000000000
Benchmark
goos: darwin
goarch: amd64
pkg: example.com/benchmark
BenchmarkConcatWithPlusOperator-8 1000000000 0.273 ns/op
BenchmarkConcatWithJoin-8 1000000000 0.283 ns/op
PASS
ok example.com/benchmark 0.774s
goarch: amd64
pkg: example.com/benchmark
BenchmarkConcatWithPlusOperator-8 1000000000 0.273 ns/op
BenchmarkConcatWithJoin-8 1000000000 0.283 ns/op
PASS
ok example.com/b
goarch: amd64
pkg: example.com/benchmark
BenchmarkConcatWithPlusOperator-8 1000000000 0.273 ns/op
BenchmarkConcatWithJoin-8 1000000000 0.283 ns/op
PASS
ok
goarch: amd64
pkg: example.com/benchmark
BenchmarkConcatWithPlusOperator-8 1000000000 0.273 ns/op
BenchmarkConcatWithJoin-8 1000000000 0.283 ns/op
PASS
goarch: amd64
pkg: example.com/benchmark
BenchmarkConcatWithPlusOperator-8 1000000000 0.273 ns/op
BenchmarkConcatWithJoin-8 1000000000 0.283 ns/op
这些输出结果包含了每个基准测试函数的运行次数和平均每次运行的时间。我们可以通过这些结果来比较不同实现方式的性能。
基准测试是优化代码性能的有用工具。通过基准测试,我们可以了解代码在不同输入情况下的性能表现,并帮助我们找出瓶颈和优化的机会。当进行性能优化时,务必要依据实际的基准测试结果,而不是只凭直觉或猜测。
感悟
学习了go语言的并发,依赖和测试,对于测试我以前没有研究过,听完课后很震撼,这两节课让我感觉从一个初级程序员看到了升级的方向,但是还是有很多地方要反复琢磨,复习。
下一篇
对于gin框架实现的增删改查,
敬请期待!!!