借着青训营的机会学习一下Go
1 并发相关
1.1 Go Routine
// TODO: Go Routine的严谨解释
可以简单理解为,go func()就创建了一个Go Routine。它是一个协程,协程之间切换比进程之间切换,负担要小得多。
那么在下面的代码中,运行之后能不能输出十条frog!呢?
package sample2
func sample1(){
fmt.println("frog!")
}
func sample2(){
for i := 0; i < 10; i++ {
go sample1()
}
}
答案是否定的。执行完for循环后,整个程序会直接退出,其启动的所有协程也被终止。
那么要怎么样才能保证,只有在十个协程都执行完之后,程序才退出呢?这时就要用到WaitGroup了。
WaitGroup可以简单理解为一个计数器。计数没有到0的时候,调用Wait方法就会一直阻塞。
func Hello(i int) {
println("hello goroutine : " + fmt.Sprintf("%d", i))
}
func HelloGoRoutine() {
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func(j int) {
defer wg.Done()
Hello(j)
}(i)
}
wg.Wait()
println("main goroutine done")
}
func main() {
HelloGoRoutine()
}
如果不加wg.Wait(),那么创建完十个协程之后HelloGoRoutine函数就会连带着其创建的协程一起退出。
加锁的重要性
试想有一个函数,它的功能就是将一个全局变量+1。然后我们用go关键字在短时间内新建10000个协程,去调用这个函数,最后全局变量会被加10000吗?答案是不一定,因为我们没有加锁,这就可能导致race condition。
2 测试相关
假设我们有一个basicTest模块。模块下有一个basic子包,其中实现了某些函数。接下来我们希望测试这个子包中的函数。
首先,在go.mod下声明整个模块:
module basicTest
go 1.22.3
在上面的例子中:
- 我们声明了整个包的名称为basicTest。
- 我们设置了所使用的go的版本为1.22.3.
接下来,我们需要编写basic子包中的业务逻辑代码和测试代码。go的测试代码要求其文件名为 <欲测试代码>_test.go。比如说,我们写一个basic.go,那么它的测试代码就是basic_test.go。
下面是一个单元测试的例子:
func TestHelloTom(t *testing.T) {
got := HelloTom()
want := "Tom"
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
在上面的例子中:
- 我们要测试basic.go中的HelloTom函数。测试函数A的函数的命名格式为
TestA。 - 我们引入了一个testing.T类型的指针变量。这是为了减少系统资源占用(比如一些io buffer就不用每个单元测试都声明一遍了)。
- 具体的单元测试逻辑肯定要我们自己写。如果结果与预期的不一致,那么就需要用刚才提到的testing.T类型的指针变量输出Error。