DAY2:Go语言进阶|青训营笔记

65 阅读3分钟

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

一、本堂课重点内容:

  • 并发编程
  • 依赖管理
  • 单元测试
  • 项目实战

二、详细知识点介绍:

  • Go语言并发的一大特点是使用协程 Goroutine,其特色在于处于用户态,开销低,相当于一个轻量级的线程。Go语言可以通过在函数前加go命令来轻松创建协程。
    func closure1() {
            for i := 0; i < 3; i++ {
                    go func(j int) {
                            println(j)
                    }(i)
            }
  • Go提倡通过通信来共享内存(使用Channel,而非临界区)。下面是Channel的创建(make)和通信(<-)用法。

     make(chan 元素类型,[缓冲大小]) //Channel的创建方式
//一小段Channel实例
        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。 使用 var 变量名 sync.mutex创建锁,使用锁名.lock()锁名.unlock()来加锁、解锁。

  • WaitGroup。为了保证主程序等待协程运行完毕再结束,我们先前使用sleep来实现。然而这种方式是不优雅的,使用WaitGroup可以更好地解决这一问题。

    其本质是维护一个计数器。Add初始化,Done减一,Wait到计数器为0时结束。

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()
}
  • go.mod 类似于 Maven ,用于管理依赖版本。

  • 测试。测试按成本从小到大有单元测试、集成测试、回归测试。其中成本与覆盖率成反比,就是说成本越低的覆盖率却越高。因此单元测试十分重要。

    单元测试有几点:

      1. 所有的测试文件以_test.go结尾。
      1. 测试文件中的测试函数形式为func TestXxx(t *testing.T) {}
      1. 初始化逻辑放在 TestMain 中。测试从TestMain进入,依次执行测试用例,最后从TestMain退出。
func TestMain(m *testing.M) {
	//测试前工作
        code := m.run()
        //测试后工作
        os.Exit(code)
}
  • 单元测试覆盖率。要查看单元测试覆盖率需要在go test 文件名.. 后加上--cover
  • 单元测试 - Mock 。即打桩,用B函数替换A函数,完事后再换回原A函数(一般defer)。
  • 基准测试。可以测试性能,用于优化代码,Go 内置有基准测试框架。测试函数形式为func BenchmarkXxx(b *testing.B) {}

三、课后个人总结:

实战项目外主要遇到几个问题:

func closure() {
	for i := 0; i < 3; i++ {
		go func() {
			println(i)
		}()
	}
	time.Sleep(3 * time.Second)
}

func closure1() {
	for i := 0; i < 3; i++ {
		go func(j int) {
			println(j)
		}(i)
	}
	time.Sleep(3 * time.Second)
}

上面两个函数乍一看相同,实际上第一个结果错误,第二个正确。原因在于第一个直接调用了外部循环变量,主函数循环很快结束了,而协程才开始打印,因此与预期结果不同。第二个例子用闭包解决了这一问题。

  • 一些小问题:

      1. os打开的文件需要关闭,部分流需要关闭,tcp连接需要关闭,bufio不需要关闭。
      1. go testgo run 命令后如有多个文件名需要把每个文件名的前缀都写清楚。