这是我参与「第五届青训营」伴学笔记创作活动的第2天
Go语言进阶
并发编程
线程 协程
线程属于内核态,创建,切换,停止都属于很重的系统操作,比较消耗资源。栈MB级别,在当今互联网高并发场景下,为每个任务创建一个线程是不现实的,因为会消耗大量的内存
协程属于用户态,轻量级线程,线程可以并发的跑多个协程,协程创建和调度由Go语言去完成,栈KB级别,Go语言一次可以创建上万个协程,这就是Go语言更适合高并发场景的原因所在
-
如何开启协程
Gorountine是Go语言程序并发的执行体要开启Gorountine只需要在调用函数的时候,在函数前面加上
go关键字即可go 函数名( 参数列表 )
协程间通信 通道(Channel)
Go提倡通过通信共享内存而不是共享内存而实现通信
项目中应该避免对共享内存进行非并发安全的读写操作
Go其实也保留着通过共享内存来实现通信的机制
Channel把Gorountine做了个连接,就像是传输队列,遵循着先入先出,能保证收发数据的顺序,是可以让一个Gorountine发送特定的值到另一个Gorountine的一个通信机制
-
Channel
Channel的创建需要通过
make关键字make(chan 元素类型[缓冲区大小])根据缓冲区大小,又可分为
- 无缓冲通道 make (chan int)
- 有缓冲通道 make (chan int,2)
Sync
-
并发安全 Lock
lock sync.Mutex lock.Lock() //加锁,获取临界区资源 lock.Unlock() //释放临界区资源
-
WaitGroup
可以实现并发任务的同步,
sync包下内部维护了一个计数器,计数器的值可以增加和减少
三个方法:
Add(delta int):计数器+deltaDone():计数器-1Wait():阻塞直到计数器为0
依赖管理
Go依赖演进
GOPATH
Go Vendor
Go Module(广泛应用)
演进目的:
- 不同环境(项目)依赖的版本不同
- 控制依赖库的版本
-
GOPATH
-
环境变量 $GOPATH
项目的工作区
- bin:项目编译的二进制文件
- pkg:项目编译的中间产物,加速编译
- src:项目源码
-
项目代码直接依赖src下的代码
-
go get 下载最新版本的包到src目录
-
弊端:
- 无法实现package的多版本控制
-
-
Go Vendor
-
项目目录下增加vendor
vendor下存在当前项目依赖的副本
-
依赖寻址方式:
vendor => GOPATH
-
弊端
- 无法控制依赖的版本
- 更新项目又可能出现以来冲突,导致编译出错
-
-
Go Module
- 通过
go.mod文件管理依赖包版本 - 通过
go get/go mod指令工具管理依赖包
-
终极目标:
定义版本规则和管理项目依赖关系
- 通过
依赖管理三要素
- 配置文件,描述依赖:
go.mod - 中心仓库管理依赖库:
Proxy
- 本地工具:
go get/mod
依赖配置
1. go.mod
由三部分构成
-
模块路径
-
Go原生库的版本号
-
单元依赖
依赖标识:[Module Path][Version/Pseudo-version]
2. version
GOPATH和Go Vendor都是源码副本方式进行依赖,没有版本规则的概念
Go Module为了方便做版本管理,定义了自己的版本规则
-
语义化版本
${MAJOR}.${MINOR}.${PATCH}MAJOR:大版本,不同的MAJOR版本是可以不兼容的,不同的MAJOR间是代码隔离的
MINOR:新增函数或者功能,需要保持在MINOR下保持前后兼容
PATCH:做代码BUG修复
V1.3.0
V2.3.0
-
基于 commit 伪版本
vx.0.0-yyyymmddhhmmss-abcdefgh1234版本前缀:和语义化版本是一样的
时间戳:提交的时间戳
十二位哈希码前缀
提交后,Go默认生成
3. indirect
在go.mod中,对于没有直接依赖的依赖模块就会标记为非直接依赖,用indirect标识出来
4. incompatible
在go.mod中
- 主版本2+模块会在模块路径增加
/vN后缀 - 对于没有go.mod文件并且有主版本2+的依赖,会加上
+incompatible后缀
依赖分发
1. 回源
Github、SVN...
-
无法保证构建稳定性
增加/修改/删除软件版本
-
无法保证依赖可用性
删除软件
-
增加第三方压力
代码托管平台负载问题
2. Proxy
一个服务站点,缓存原站中的软件内容,缓存的版本不会改变
保证依赖的稳定性,实现稳定可靠的依赖分发
-
变量 GOPROXY
通过GOPROXY环境变量来配置Proxy
服务站点URL列表,用,分割
最后
direct表示源站,如果前面那些站点都没有依赖的话,会回源到第三方代码平台上去
工具
1. go get
go get example.org/pkg:拉取MAJOR版本最新commit
@update:等于不加
@none:删除依赖
@v1.1.2: tag版本,语义版本
@23dfdd5: 特定的commit
@master: 分支的最新commit
2. go mod
init:初始化项目的时候,需要用init创建go.mod文件
download:下载模块到本地缓存,把所有的依赖拉下来
tidy:增加需要的依赖,删除不需要的依赖
测试
单元测试
1.规则
- 所有测试文件以
_test.go结尾
- 函数命名
func TestXxx(t *testing.T)
-
初始化逻辑放到
func TestMain(m *testing.M)中测试前:数据装载、配置初始化等前置工作
code := m.Run()
测试后:释放资源等收尾工作
go test [flags][packages]
2. assert
github.com/stretchr/testify/assert
很多开源的assert包可以帮助实现equal或not equal的问题
3. 覆盖率
代码覆盖率可以用来评估单元测试
加上--cover参数
- 一般覆盖率:50%~60%,较高覆盖率:80%+
- 测试分支相互独立、全面覆盖
- 测试单元粒度足够小,函数单一职责
4. Mock
单元测试有两个目标:稳定&幂等
幂等:重复运行一个测试的case的时候,结果是一样的
稳定:单元测试是能相互隔离的,能在任何时间,任何函数运行
为了实现目的,单元测试中需要使用Mock机制,使得测试可以在任何时间,任何环境执行
Mock组件monkey:github.com/bouk/monkey
Patch(原函数(目标被替换的函数), 需要打桩的函数):打桩函数
Unpatch(原函数):测试结束后,需要把桩卸载掉
基准测试
测试一段程序运行时的性能和CPU的损耗
- 函数命名
BenchmarkXxx(b *testing.B) b.ResetTimer:重置定时器
- 基准测试的并行通过
b.RunParallel来实现
- 优化随机函数性能问题:
fastrand,rand在高并发场景会造成一定性能问题
实践
在实践出现连接失败的问题:
connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
只要配置一下代理就可以解决了
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct