这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天。
并发编程
Go语言中不使用提供线程操作API,而是通过操作协程Goroutine来进行并发编程。协程比线程更加轻量化,与线程是一对多的关系,使用协程可以实现更高并发量。
将一个函数放在新的goroutine中启动,只需要使用go关键字,操作非常简单。
go func() {
fmt.Println("Hello world!")
}()
Hello world!
许多语言通过共享内存在线程间共享数据,但Go提倡通过通信共享内容。通信借助于channel。
创建带(不带)缓存的channel,使用make关键字。创建完毕后,使用<-发送或收取数据。发送完毕后通过.close() 关闭通道。一个关闭了的通道仍可以从中接收数据,但不能写入。range关键字也可用于通道,从通道中逐步读取内容,直到通道关闭。
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)
}
}
基于共享内存的并发编程在Go中依然存在。使用sync.Mutex进行上锁解锁,即可安全地共享内存。
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
Go Module
曾经Go的依赖在执行go get后统一安装到%GOPATH%/src下,但是这种依赖管理的方式无法解决不同项目有不同版本的依赖的问题。
现在通常使用Go Module机制来管理依赖。在go.mod文件中显式地管理每个项目单独的依赖。
go.mod
go.mod文件分为三部分内容
module <模组名>
go.xx Go版本
requires(
<依赖>
<间接依赖> // indirect
)
依赖的版本信息可以分为
- 语义化版本:
${MAJOR}.${MINOR}.${PATH} - 基于commit伪版本:
vX.0.0yyyymmddhhmmss-abcdefg1234
对于go 1.11之前的一些模组,行末会表明+incompatible。
依赖分发需要通过proxy进行。如果无法下载依赖,可以考虑换一个代理。
go工具
通过go get命令安装依赖
go get example.org/pkg
// @update 默认
// @none 删除依赖
// @v1.1.2 tag版本
// @23dfd5 特定commit版本
// @master 分支的最新commmit
通过go mod命令管理依赖
go mod +
// init 初始化项目
// download 下载依赖到本地
// tidy 清理不需要的依赖,下载需要的依赖
测试
测试是很多新手程序员不重视,但是实际上非常重要的内容。Golang在语言层面支持测试。
主要进行以下三种测试:
- 回归测试:QA通过终端回归测试一些主流的使用场景
- 集成测试:对系统功能维度作测试验证
- 单元测试:对单独的模块、函数做功能验证
单元测试
命名规则:
- 所有测试文件以
_test.go结尾 - 函数定义为
func TestXxx(*testing.T) - 主要测试逻辑放在函数体中
func HelloTom() string {
return "Jerry"
}
func TestHelloTom(t *testing.T) {
expected := "Tom"
output = HelloTom()
if expected != output {
t.Errorf("Expected %s found %s", expected, output)
}
}
运行测试时,使用go test <flags> <packages>运行。
常常使用assert库进行测试。assert库在很多语言中都存在,在go中则为第三方库。
基准测试
基准测试对程序的性能进行测试,go语言内置的框架支持基准测试。
项目实践
今天的项目是一个实现常见C/S服务端接口的项目,主要功能是展现话题和回帖列表。
比较遗憾的是没有用到数据库,在文件层面上读取还是有点脱离实际场景。