这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
语言进阶
协程:用户态,轻量级线程,栈空间KB级别,切换开销小 线程:内核太,一个线程可以运行多个协程,栈空间MB级别
启动一个协程
在go里面启动一个协程非常方便简单,只需要在调用函数前添加go关键字
func sayHello(i int) {
fmt.Println("hello :", i)
}
func Goroutine() {
for i := 0; i < 5; i++ {
go func(j int) {
sayHello(j)
}(i)//匿名函数后跟括号,表示立即执行该函数,括号内是匿名函数对应的需要的参数
}
time.Sleep(time.Second)
}
可能的运行结果:
hello : 3
hello : 0
hello : 1
hello : 4
hello : 2
什么是Channel
channel是Go语言中的一个核心类型,可以把它看成管道。并发核心单元通过它就可以发送或者接收数据进行通讯,这在一定程度上又进一步降低了编程的难度。
channel是一个数据类型,主要用来解决go程的同步问题以及协程之间数据共享(数据传递)的问题。
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。goroutine 奉行通过通信来共享内存,而不是共享内存来通信。引用类型 channel可用于多个 goroutine 通讯。其内部实现了同步,确保并发安全。
创建一个Channel
make(chan 元素类型, [缓冲大小])
- 无缓冲通道 make(chan int)
- 有缓冲通道 make(char int, 2)
无缓冲通道:当读端正在使用时,写端被阻塞,所以一次只能传输一个数据
无缓冲通道:当缓冲区被读空时,才会被阻塞;当缓冲区被填满后,写端才会阻塞
下面这个例子中通过生产者协程发送0~9的数字
再通过消费者协程计算输入数字的平方
最后再主协程中输出结果
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() { //生产者
defer close(src)//关闭channel
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)
}
}
并发安全
控制并发安全,我们一般都是使用的mutex来对共享内存进行加锁访问
下面让我们看一下go中如何实现
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func Add() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("without lock:", x)
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("with lock:", x)
}
可能的结果:
without lock: 7489
with lock: 10000
WaitGroup
我们常使用WaitGroup来阻塞主协程,直到我期待的子协程全部执行完毕
func GoWaitGroup() {
var wg sync.WaitGroup
goroutineCnt := 5
wg.Add(goroutineCnt)//初始化一个计数器,如果为负数,触发panic
for i := 0; i < goroutineCnt; i++ {
go func(j int) {
defer wg.Done()//完成一个任务,将计数值减一
sayHello(j)
}(i)
}
wg.Wait()//等待所有子协程结束
fmt.Println("main goroutine is end!!!")
}
可能的结果:
hello : 4
hello : 1
hello : 2
hello : 3
hello : 0
main goroutine is end!!!
依赖管理
依赖:即他人已经开发和封装好的包,在复杂项目中,我们需要引入各种依赖
Go依赖管理的发展
graph LR
GOPATH --> Go_Vender --> Go_Module
现如今我们多用GO Module进行包管理
graph LR
go_mod --> init:初始化go.mod文件 & download:下载模块到本地 & tidy:整理依赖删除不需要的
测试
测试是软件开发流程中的最后一道坎,同时也是最重要的一个流程。
单元测试-规则
- 测试文件以 _test.go 结尾
- 测试函数以 Test 开头,且参数必须为*testing.T(如TestXxx(t *testing.T))
- 初始化逻辑放到TestMain中
func TestMain(m *testing.M){
// 数据初始化
code := m.Run()
// 资源回收
os.Exit(code)
}
package hello
func HelloTom() string {
return "Jerry"
}
package hello
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestHelloTom(t *testing.T) {
output := HelloTom()
expectOutput := "Tom"
//if output != expectOutput {
// t.Errorf("Expected %s do not match actual %s", expectOutput, output)
//}
assert.Equal(t, expectOutput, output)//两种方式进行判断
}
如果我们对返回值修改成Tom,则测试通过
单元测试-覆盖率
package passline
func PassLine(score int16) bool {
if score >= 60 {
return true
}
return false
}
package passline
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestPassLineTrue(t *testing.T) {
isPass := PassLine(70)
assert.Equal(t, true, isPass)
}
func TestPassLineFail(t *testing.T) {
isPass := PassLine(50)
assert.Equal(t, false, isPass)
}
控制台输入:
go test PassLine_test.go PassLine.go --cover
输出:
性能测试 Benchmark
- 函数名以 Benchmark 开头