Go进阶 | 青训营笔记

66 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
学习课程Go语言进阶依赖管理&&Go语言工程实践之测试

前言

本人不擅长记笔记,对一些东西都是记个大概模式和概念,markdown也是第一次用。

一、本堂课重点内容:

Goroutine和依赖管理

二、详细知识点介绍:

2.1 Goroutines

Go语言中的 goroutine 是 Go 语言中的轻量级线程,它是由 Go 运行时管理的,不需要操作系统的支持。

一个简单的 goroutine 示例:

package main

import (
    "fmt"
    "time"
)

func printHello() {
    fmt.Println("Hello World!")
}

func main() {
    go printHello()
    time.Sleep(1 * time.Second)
}

通过 go 关键字来启动一个 goroutine,执行 printHello 函数。使用 time.Sleep(1 * time.Second) 来等待 goroutine 执行完成。
在 Go 中,多个 goroutine 通过共享内存来进行通信,而不是通过消息传递。这种方式需要使用互斥锁或者其他同步机制来避免数据竞争。

goroutine 是轻量级的,创建和销毁它们的开销比线程小得多,因此在 Go 中常常使用大量的 goroutine 来实现并发编程

2.2 Channels

通道(channel)是用来传递数据的一个数据结构。可用于两个goroutine之间通过传递一个指定类型的值来同步运行和通讯。操作符<-用于指定通道的方向,发送和接收。如果未指定,则默认为双向通道
使用内置的make函数,我们可以创建一个channel:

ch := make(chan int) 

因为使用的是make,所以我们创建的channel是一个引用,零值为nil。

Channel.png 根据此图可看出,我们上方例子创建出的是一个无缓冲通道。为图例左侧样式。右侧则为设置了缓冲区的样式。

ch := make(chan int, 2)

通过make的第二个参数指定缓冲区大小。
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

2.3 Sync.Mutex互斥锁 && WaitGroup

Go语言中的Sync.Mutex互斥锁可以用来保证在多个 goroutine 中对共享资源的访问是互斥的,避免数据竞争。

package main
import (
    "fmt"
    "sync"
)

var (
    count int
    lock  sync.Mutex
)

func increment(wg *sync.WaitGroup) {
    defer wg.Done()
    lock.Lock()
    defer lock.Unlock()
    count++
    fmt.Println("Increment:", count)
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go increment(&wg)
    }
    wg.Wait()
    fmt.Println("Final:", count)
}

上面的代码中,定义了一个变量count和一个互斥锁lock,在 increment 函数中,使用 lock.Lock() 获取锁,保证在同一时刻只有一个 goroutine 能够对 count 变量进行修改,避免数据竞争。

Go语言中的sync.WaitGroup可以用来等待一组goroutine的结束。 WaitGroup.png WaitGroup为我们提供三种方法:Add() Done() Wait() var wg sync.WaitGroup 声明一个WaitGroup变量
通过wg.Add(1) 和 wg.Done() 来检测线程的完成情况,wg.Wait()表示主线程等待在wg变量上

package main

import (
    "fmt"
    "sync"
)

func worker(wg *sync.WaitGroup, id int) {
    defer wg.Done()
    fmt.Printf("Worker %d started\n", id)
    // Do some work
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(&wg, i)
    }
    wg.Wait()
    fmt.Println("All done")
}

wg.Add(1) 来记录需要等待的 goroutine 的数量,在每个 goroutine 结束时使用 wg.Done() 来标记它已经结束。最后使用 wg.Wait() 来等待所有的 goroutine 结束

2.4 依赖管理

依赖管理三要素 依赖管理三要素.png

go.mod

一个项目中可以包含多个package,不管有多少个package,这个package都将随项目一起发布

一个module的版本号规则必须遵循语义化规范(semver.org/)版本号必须使用格式v… 或v1.5.0-rc.1
Go module实际上只是精准的记录项目的依赖情况,包括每个依赖的精确版本号生成有描述依赖的go.mod和记录module的checksum的go.sum.
go.mod的内容:

  • 项目名
  • go 的版本
  • 第三方的包 go mod的指令
  1. module: 声明module名称:
  2. require: 声明依赖以及其版本号:
  3. replace: 替换require中声明的依赖,使用另外的依赖及其版本号;
  4. exclude: 禁用指定的依赖

2.5 测试

test.png 回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误。
集成测试又叫子系统测试、组装测试、部件测试等。集成测试主要是针对软件高层设计进行测试,一般来说是以模块和子系统为单位进行测试。
单元测试是指对软件中的最小可测试单元进行检查和验证。

unittest.png

go test

go test命令是一个按照一定的约定和组织来测试代码的程序。在包目录内,所有以_test.go为后缀名的源文件在执行go build时不会被构建成包的一部分,它们是go test测试的一部分。
*_test.go文件中,有三种类型的函数:测试函数、基准测试(benchmark)函数、示例函数。
go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数,生成一个临时的main包用于调用相应的测试函数,接着构建并运行、报告测试结果,最后清理测试中生成的临时文件。

测试函数

每个测试函数必须导入testing包。测试函数有如下的签名:

func TestName(t *testing.T) {
    // ...
}

测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头:

func TestSin(t *testing.T) { /* ... */ }
func TestCos(t *testing.T) { /* ... */ }
func TestLog(t *testing.T) { /* ... */ }

其中t参数用于报告测试失败和附加的日志信息。

基准测试

benchmark.png 基准测试是测量一个程序在固定工作负载下的性能。在Go语言中,基准测试函数和普通测试函数写法类似,但是以Benchmark为前缀名,并且带有一个*testing.B类型的参数;*testing.B参数除了提供和*testing.T类似的方法,还有额外一些和性能测量相关的方法。它还提供了一个整数N,用于指定操作执行的循环次数。 下面是IsPalindrome函数的基准测试,其中循环将执行N次。

import "testing"

func BenchmarkIsPalindrome(b *testing.B) {
    for i := 0; i < b.N; i++ {
        IsPalindrome("A man, a plan, a canal: Panama")
    }
}

我们用下面的命令运行基准测试。和普通测试不同的是,默认情况下不运行任何基准测试。我们需要通过-bench命令行标志参数手工指定要运行的基准测试函数。该参数是一个正则表达式,用于匹配要执行的基准测试函数的名字,默认值是空的。其中“.”模式将可以匹配所有基准测试函数,但因为这里只有一个基准测试函数,因此和-bench=IsPalindrome参数是等价的效果。

$ cd $GOPATH/src/gopl.io/ch11/word2
$ go test -bench=.
PASS
BenchmarkIsPalindrome-8 1000000                1035 ns/op
ok      gopl.io/ch11/word2      2.179s

报告显示每次调用IsPalindrome函数花费1.035微秒,是执行1,000,000次的平均时间。

三、实践练习例子:

通过前面的学习尝试实践实现一个社区话题页面。
1.需求描述

  1. 支持对话题发布回帖。
  2. 回帖id生成需要保证不重复、唯一性。
  3. 新加回帖追加到本地文件,同时需要更新索引,注意Map的并发安全问题 
    2.需求用例

example.png 3.实体图

ER.png 4.分层结构

分层结构.png 5.组件工具

kit.png

四、课后个人总结:

对于goroutine的知识学习仍需要加强。sync.Mutex互斥锁以及waitgroup还是不是很会应用,因为第一次接触并发编程。同时,对于测试部分本人第一次接触仍需要吸收。在实践例子部分,我本人没有经验,对于我来讲还是有些快,需要再花点时间去实践去理解。

五、引用参考: