Go 个人初学记录 | 豆包MarsCode AI刷题

31 阅读2分钟

借着青训营的机会学习一下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。