1 并发&并行
1.1概念
Golang 有针对性解决高并发问题。并行可以看作是一个实现高并发的手段。
- 并行:多个程序在多个CPU上运行;
- 并发:多线程程序在一个核的CPU上运行【时间片切换】。
Golang中的协程与线程的区别:
- 协程:用户态;比线程更加轻量。
- 线程:内核态;一个线程中可以跑多个协程。
在Go中,建立协程仅需在函数前添加go关键字即可。
1.2 Channel
通道:实现通过通信共享内存;通道支持先入先出保证顺序性。
channel的构建采用make函数,格式如下:make(chan 元素类型,[缓冲大小])
有缓冲的channel解决消费者消费能力不强的问题【此处和消息队列有些相似】
支持锁操作,对临界区加锁可以解决在并发过程中出现的安全性问题。【sync.Mutex】
WaitGroup【sync.WaitGroup】支持执行并发任务,提供add;Done;Wait分别实现添加任务个数、计时器减一和阻塞功能。
1.3 案例
1.3.1 channel函数案例测试
添加test测试文件,实现单元测试。
//demo1.go
package concurrence
func CalSquare() {
src := make(chan int)
dest := make(chan int, 5)
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)
}
}
//demo1_test.go
package concurrence
import "testing"
func TestCalSquare(t *testing.T) {
CalSquare()
}
通过test文件实现测试不使用go run指令,否则会报错,go run不执行带test的文件。使用go test指令测试。
go test指令会测试当前目录下全部带有test的文件。
1.3.2 WaitGroup
package concurrence
import (
"fmt"
"sync"
)
func hello(i int) {
println("hello world : " + fmt.Sprint(i))
}
func ManyGo() {
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()
}
其中,Add实现了添加1个任务。Done实现计时器逐步减一的操作。Wait实现任务完成后的阻塞活动。
2 依赖管理
这部分与Java的Maven;Python的pip|Conda类似,都是管理项目依赖工具。
2.1 发展
GOPATH->GOVENDOR->GOMODULE【最后一个是目前应用最广的】 各版本依赖工具具有的问题:
- Go path:存在多版本依赖冲突问题;
- Go vendor:添加vendor文件夹管理依赖,但是在多级依赖下,底层依赖依然会出现版本冲突问题。
- Go module:通过go.mod文件管理依赖包。通过
go get或go mod指令实现管理。
2.2 go.mod
在第一篇文章搭建go环境时,测试过程中对项目进行了初始化,然后产生go.mod文件,因此生成流程可参考之前内容。
go.mod文件中效果如下:
- 如上图所示,require()中的依赖项有 //indirect 标识。这个标识是因为存在间接依赖。【a依赖b,b依赖c,a间接依赖c】
- 对于没有go.mod文件且主版本在2+的依赖,会添加+incompatible后缀。【可能有依赖冲突】
- go会选择最低兼容版本进行使用。【如果a依赖b,a依赖c,b依赖dv1.0,c依赖dv1.1,在d的两个版本兼容的情况下,会选择dv1.1版本】
2.3 依赖分发
在导入依赖的过程中,依赖可能来源于Github、SVN等源,在没有任何代理的情况下,会存在以下问题:
- 不保证稳定性【代码版本可能被删】;
- 不保证依赖可用;
- 增强第三方负载。
因此引入Proxy解决该问题,保证依赖项的稳定可用。【proxy1;proxy2;...;源站依次查询】
go get 工具:
- @update 默认
- @none 删除依赖
- @v版本号 tag版本、语义版本
- @23dfdd5 特定commit
- @master 分支最新commit
go mod 工具:
- init:初始化
- download:下载模块【拉依赖】
- tidy:增加需要依赖,删除不需要的依赖
3 测试
3.1 单元测试
单元测试覆盖范围最广,基本是开发人员对函数、接口等内容的基础测试。
参考1.3中测试文件名,在单元测试中,文件名固定,都是在功能文件基础上补充“_test”结尾拼接成新文件。函数结构如下:
func TestXxx(* testing.T)
初始化逻辑放到TestMain中.【存在很多工具包支持equals等函数】
测试需要有较高的覆盖率【通常50~60;较高的为80%+】 代码构建过程中尽量小粒度,容易测试。
单元测试的目标:幂等【测试结果相同】、稳定【相互隔离】 比如,测数据库需要网络连接数据库,这样有网络对性能的影响,所以不稳定。
3.2 Mock测试
可以使用Mock函数【monkey.Patch;monkey.Unpatch】进行测试。为一个函数/方法打桩。
3.3 基准测试
主要针对性能方面。 函数以BenchmarkSelect开头
优化测试性能,可以使用fastrand,高并发场景可能会有一定的性能问题。
4 引入gin框架案例
- 初始化项目
go mod init page_ex
go get -u github.com/gin-gonic/gin
直接获取gin可能会报错,由于部分包在外网,难以访问,因此需要换源。【流程参考:www.cnblogs.com/xiaoyingzha…
成功换源后重新执行指令go get -u github.com/gin-gonic/gin【按照gin官网描述获取,参考gin-gonic.com/zh-cn/docs/…
- 构建测试demo1,代码如下:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// GET:请求方式;/hello:请求的路径
// 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数
r.GET("/hello", func(c *gin.Context) {
// c.JSON:返回JSON格式的数据
c.JSON(200, gin.H{
"message": "Hello world!",
})
})
// 启动HTTP服务,默认在0.0.0.0:8080启动服务
r.Run()
}
- 打开终端输入go run指令启动服务。
- 使用浏览器访问localhost:8080/hello即可获取一个简单msg。
补充,成功导入gin框架后,go.mod中包含的内容如上述给出的go.mod示例图。