这是我参与「第五届青训营 」笔记创作活动的第2天
只不过是字节给我的任务罢了
Go并发
Golang使用Goroutine实现轻量级线程(协程),运行在用户态,栈KB级别
使用Goroutine
使用go
关键字创建一个协程,只需要在调用函数语句之前加上go
关键字即可
go funcName
主协程结束,所有其它协程一起被终止
package main
import (
"fmt"
"time"
)
func main() {
go spinner(100 * time.Millisecond)
const n = 45
fibN := fib(n) // slow
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
for {
// 如果有这个死循环,goroutine spinner()还会继续运行,主线程结束后,sipnner才会结束
}
}
func spinner(delay time.Duration) {
for {
for _, r := range `-|/` {
fmt.Printf("\r%c", r)
time.Sleep(delay)
}
}
}
func fib(x int) int {
if x < 2 {
return x
}
return fib(x-1) + fib(x-2)
}
协程间的通信
通道通过通信共享内存,而非通过共享内存实现通信(这种是临界区)
通道相当于将协程做了一个连接,实现协程之间的通信
Channel
goroutine是golang中在语言级别实现的轻量级线程,仅仅利用 go 就能立刻起一个新线程。多线程会引入线程之间的同步问题,在golang中可以使用channel作为同步的工具。 通过channel可以实现两个goroutine之间的通信。 创建一个channel, make(chan TYPE {, NUM}) , TYPE指的是channel中传输的数据类型,第二个参数是可选的,指的是channel的容量大小。 向channel传入数据, CHAN <- DATA , CHAN 指的是目的channel即收集数据的一方, DATA 则是要传的数据。 从channel读取数据, DATA := <-CHAN ,和向channel传入数据相反,在数据输送箭头的右侧的是channel,形象地展现了数据从‘隧道’流出到变量里。
使用make(chan TYPE {, NUM})
函数创建通道(类型为chan
),如果有提供了NUM
(缓冲大小),那么这个通道则为有缓冲通道,反之是无缓冲通道(同步通道)
示例:
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) // 结果为依次输出 0 ~ 9 的相乘
}
}
并发安全
可以使用lock.Lock()
对临界区加锁,使用lock.Unlock()
解锁,释放临界区资源,使用方法同POSIX的pthread_mutex_lock/unlock
WaitGroup
使用WaitGroup控制主协程的等待时间
Add(delta int)
实现计数器+delta
Done()
实现计数器+1
Wait()
阻塞到计数器为0
Go依赖库管理
GOPATH
cd $GOPATH
ls
$: bin/ pkg/ src/ #项目编译的二进制文件;编译过程的中间产物,加速编译;源码
go get
将最新版包下载到src目录下
GOPATH的弊端:无法实现package多版本控制
GoVender
在项目目录下增加vender文件,所有依赖包副本形式放在$ProjectRoot/vender
依赖寻址方式:vender=>GOPATH
GoVender的弊端:无法控制依赖版本,更新项目有可能出现依赖冲突,导致编译出错
Go Module
通过go.mod
文件管理依赖包版本
通过go get/go mod
指令工具管理依赖包
目的:定义版本规则和管理项目依赖关系
依赖管理三要素
- 配置文件,描述依赖:
go.mod
- 中心仓库管理依赖库:
Proxy
- 本地工具:
go/get mod
依赖配置-go.mod
依赖标识:[Module Path][Version/Pseudo-version]
module example/project/app # 依赖管理基本单元,可以设置为github目录
go 1.16 # 原生库
require ( # 单元依赖
example/lib1 v1.0.2example/lib2 v1.0.0 // indirect
example/lib3 v0.1.0-20190725025543-5a5fe074e612
example/lib4 v0.0.0-20180306012644-bacd9c7ef1dd // indirect
example/lib5/v3 v3.0.2
example/lib6 v3.2.0+incompatible
)
依赖配置-version
语义化版本
${MAJOR}.${MINOR}.${PATCH}
例:V1.3.0
基于commit伪版本
vX.0.0-yyyymmddhhmmss-abdcefgh1234
语义化版本-时间戳-哈希码
依赖配置-indirect
A依赖B,B依赖C,那么A直接依赖B,B直接依赖C,A间接依赖C
依赖配置-incompatible
主版本2+模块 会在模块路径后加/vN
后缀
对于没有go.mod
文件并且主版本2+的依赖,会+incompatible
依赖配置-依赖图
选择最低的兼容版本v1.4
依赖分发-回源
0. 无法保证构建稳定性
0. 无法保证依赖可用性
0. 增加第三方压力(代码托管平台的负载)
依赖分发-Proxy
使用Proxy保证依赖稳定性,直接从Proxy中拉取依赖
变量GOPROXY
GOPROXY="https://proxy1.cn, https://proxy2.cn, direct"
其中direct
代表源站
首先从proxy1中拉取依赖,没有则拉取proxy2,还是没有则拉取源站direct
工具-go get
go get example.org/pkg @TAG
@TAG
可以是
- @update 默认
- @none 删除依赖
- @v1.1.2 tag版本,语义版本
- @23dfcd5 特定的commit
- @master 分支的最新commit
工具-go mod
go mod TAG
TAG
可以是
- init 初始化,创建go.mod文件
- download 下载模块到本地缓存
- tidy 增加需要的依赖,删除不需要的依赖
测试
- 回归测试
- 集成测试
- 单元测试
单元测试
输入->输出 \
>校对
期望/
单元测试-规则
所有的测试文件以_test.go
结尾
测试函数定义func TestXxx(t *testing.T)
初始化逻辑放在TestMain
中
func TestMain(m *testing.M) {
// 测试前,数据装载,配置初始化等前置工作
code := m.Run() // 运行包内的所有单元测试
// 测试后,释放资源
os.Exit(code)
}
单元测试-依赖
外部依赖=>稳定&&幂等=>Mock
assert
assert包中包含了Equal、NotEqual等等,我们可以调用asset包来简化代码
import ("github.com/stretchr/testify/assert")
使用assert判断运行结果和期望值是否想等
assert.Equal(t, expectOutput, output)
单元测试-Mock
- 为一个函数打桩
- 为一个方法打桩
使用函数A替换函数B,那么A就是打桩函数
func Patch(target, replacement interface{}) *PatchGuard {
t := refflect.ValueOf(target)
r := reflect.ValueOf(replacement)
patchValue(t, r)
return &PatchGuard{t, r}
}
•
func Unpatch(target interface{}) bool {
return unpatchValue(reflect.ValueOf(target))
}