这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
前言
需要一定的go语言基础,本文基于字节跳动青训营第二天课程进行学习并做出记录,go基础想速通可以看看我的第一篇文章go语言基础学习 | 青训营笔记 - 掘金 (juejin.cn)
Goroutines
goroutine可以简单地类比作一个线程。当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main goroutine。新的goroutine会用go语句来创建。在语法上,go语句是一个普通的函数或方法调用前加上关键字go。go语句会使其语句中的函数在一个新创建的goroutine中运行。
f() // 这个函数会执行直到它返回结果
go f() // 创建一个goroutine,然后继续往下执行
goroutine 保证数据一致性的方法
sync.Mutex 锁机制
goroutine不保证线程安全,所以使用时可以增加锁来确保一致性。
如果不加锁,假设有五个goroutine,五个goroutine的x值都为100,其中一个拿到x = 100后还没操作,其余四个goroutine先执行了x++,那么全局的的x = 104,第一个goroutine还是x = 100,再到这个goroutine执行x++,最后得到了x = 101的结果,不符合我们的想法。用一个简单实例演示如何解决,如果对未加锁的结果感兴趣,可以自己动手试试。
package main
import (
"sync"
"time"
)
var (
mu sync.Mutex // 一个锁对象
x int
)
func main() {
for i := 0; i < 100; i++ {
go show()
}
time.Sleep(time.Second)
println(x)
}
func show() {
for i := 0; i < 100; i++ {
mu.Lock()
x++
mu.Unlock()
}
}
sync.WaitGroup
Add方法用于设置 WaitGroup 的计数值,可以理解为子任务的数量Done方法用于将 WaitGroup 的计数值减一,可以理解为完成一个子任务Wait方法用于阻塞调用者,直到 WaitGroup 的计数值为0,即所有子任务都完成
package concurrence
import "sync"
func ManyGoWait() {
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()
}
Channel 通道
- channel本身是一个队列,先进先出
- 线程安全,不需要加锁
- 本身是有类型的,string, int 等,如果要存多种类型,则定义成 interface类型
- channel是引用类型,必须make之后才能使用,一旦 make,它的容量就确定了,不会动态增加!!它和map,slice不一样
在任何时候,同时只能有一个 goroutine 访问通道进行发送和获取数据。goroutine 间通过通道就可以通信。
下面是示例代码
package main
import "time"
func main() {
src := make(chan int)
dest := make(chan int, 3) // 加上缓冲区,因为 dest 的调用比 src 要麻烦的多,为了不让 src 有等待时间,设置缓冲区
go func() {
defer close(src)
for i := 0; i < 50; i++ {
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
go func() {
for i := range dest {
println(i) // 输出0到四十九的平方数
}
}()
time.Sleep(time.Second)
}
go.mod
类似maven 和 gradle 的一个包管理工具
go.mod 提供了module, require、replace和exclude四个命令
- module语句指定包的名字(路径)
- require语句指定的依赖项模块
- replace语句可以替换依赖项模块
- exclude语句可以忽略依赖项模块
require 关键字是引用,后面是包,最后v1.0.2 是引用的版本号 如果后面带有 indirect 意味着不是直接依赖,是依赖的包所依赖的库
require (
bou.ke/monkey v1.0.2
}
单元测试
什么是单元测试
单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类、超类、抽象类等中的方法。单元测试就是软件开发中对最小单位进行正确性检验的测试工作。
golang的单元测试
测试函数
- 测试文件以 _test.go 结尾
- 函数规则为
func TestXxx(t *testing.T) - 初始化逻辑放到
func TestMain(m testing.M)中。例如:测试前进行数据的装载,测试后需要释放一些资源,即测试方法前后需要执行的一些命令。
每个测试用例可能并发执行,使用 testing.T 提供的日志输出可以保证日志跟随这个测试上下文一起打印输出。testing.T 提供了几种日志输出方法,详见下表所示。
| 方 法 | 备 注 |
|---|---|
| Log | 打印日志,同时结束测试 |
| Logf | 格式化打印日志,同时结束测试 |
| Error | 打印错误日志,同时结束测试 |
| Errorf | 格式化打印错误日志,同时结束测试 |
| Fatal | 打印致命日志,同时结束测试 |
| Fatalf | 格式化打印致命日志,同时结束测试 |
除了自带的日志方法,还可以使用第三方的库来提高测试的效率,由于博主也是小白,这里不做推荐
测试覆盖率
对待测程序执行的测试的程度称为测试的覆盖率。测试覆盖率并不能量化——即使最简单的程序的动态也是难以精确测量的——但是有启发式方法来帮助我们编写有效的测试代码。
语句的覆盖率是最简单和最广泛使用的。语句的覆盖率是指在测试中至少被运行一次的代码占总代码数的比例。
使用 go test -cover 即可得到测试结果和语句覆盖率,在项目中覆盖率的期望是不低于50%,理想状态是80%,不能盲目追求百分百