这是我参与「第五届青训营 」笔记创作活动的第2天
1. Go语言并发编程
1.1 Goroutine
go语言协程的特点
- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制
- 协程是轻量级的线程
创建goroutine
在函数调用语句之前加go关键字即创建开启并发单元
package main
import (
"fmt"
"strconv"
"time"
)
//编写一个函数,每隔一秒输出 "hello,world"
func test() {
for i := 1; i <= 10; i++ {
fmt.Println("test hello,world " + strconv.Itoa(i))
time.Sleep(time.Second)
}
}
func main() {
go test() //开启了一个协程,使其同时执行
for i := 1; i <= 3; i++ {
fmt.Println("mian() hello,world " + strconv.Itoa(i))
time.Sleep(time.Second)
}
//输出结果:
// mian() hello,world 1
// test hello,world 1
// test hello,world 2
// mian() hello,world 2
// test hello,world 3
// mian() hello,world 3
}
说明
Go语言的协程是抢占式调度的,当遇到长时间执行或者进行系统调用时,会主动把当前goroutine的CPU 转让出去,让其他goroutine能被调度并执行。
1.2 Channel
创建Channel
Channel的定义有三种类型,可接受和发送某数据类型的数据、只接受某数据类型的数据和只发送某数据类型的数据。make函数可以初始化Channel并设置缓冲容量。
package concurrence
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
//创建两个通道,一个用于发送数字,一个用于接收数字
//发送数字到src通道
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
//从src通道中取出数字,计算平方,发送到dest通道
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
println(i)
}
}
说明
Channel是Go中的一个核心类型,可以把它看成一个管道,通过它并发核心单元就可以实现协程之间的内存共享,实现通信。
1.3 Sync
并发安全
为什么会有并发安全问题?
并发编程的并发体在同一个时间内访问共享数据,如果处理不当,就会导致实际结果偏离理论结果,影响程序的运行。
案例说明
通过互斥锁来保证并发安全
package main
import (
"sync"
"time"
)
var (
x int
lock sync.Mutex
)
func addwithlock() {
lock.Lock()
x++
lock.Unlock()
}
func addwithoutlock() {
x++
}
func main() {
x = 0
for i := 0; i < 100000; i++ {
go addwithlock()
}
time.Sleep(time.Second)
println("x with lock:", x)
x = 0
for i := 0; i < 100000; i++ {
go addwithoutlock()
}
time.Sleep(time.Second)
println("x without lock:", x)
//输出结果:
// x with lock: 100000
// x without lock: 94542
}
WaitGroup
WaitGroup是go语言中一个用来计数的信号量,可以用来解决部分并发安全问题。
案例说明
package concurrence
import (
"fmt"
"sync"
)
func hello(i int) {
println("hello world : " + fmt.Sprint(i))
}
func ManyGo() {
var wg sync.WaitGroup
//创建一个等待组
//通过控制信号量来控制goroutine的数量
for i := 0; i < 5; i++ {
wg.Add(1)
go func(j int) {
defer wg.Done()
//goroutine执行完毕,将信号量减1
hello(j)
}(i)
}
wg.Wait()
//等待所有的goroutine执行完毕
}
2. Go语言依赖管理
2.1 GOPATH
GOPATH目录下一共包含三个子目录
- bin:存储所编译生成的二进制文件。
- pkg:存储预编译的目标文件,以加快程序的后续编译速度。
- src:存储所有.go文件或源代码。
弊端:无法传递版本信息,无法实现package的多版本控制
2.2 Go Vendor
vendor是go语言的包管理工具,它会把依赖包的副本存到vendor目录下,使得其他机器在编译的时候不用再下载相关依赖,但是无法解决版本变动和项目使用同一包的不同版本问题。
2.3 Go Module
- 通过go.mod文件管理依赖包版本
- 通过go get/mod指令工具管理依赖包
go mod 基本操作
go mod init初始化当前文件夹,创建 go.mod 文件
go mod edit编辑 go.mod 文件
go mod tidy增加缺少的包,删除无用的包
go mod download下载依赖包到本地(默认在 GOPATH/pkg/mod 目录)
go mod graph打印模块依赖图
go mod vendor将依赖复制到 vendor 目录下
go mod verify校验依赖
go list -m all查看所有的依赖