2.1 Gorountine - 协程
2.1.1 并发与并行
并发:多线程程序在一个核的CPU上运行
并行:多线程程序在多个核的CPU上运行
Goroutine,协程(用户态,轻量级,栈KB级别),线程(内核态,跑多个协程,栈MB级别)
Go语言中开启协程案例:
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 5; i++ {
go func(j int) { // 使用go标识符创建协程
fmt.Println(j)
}(i)
}
time.Sleep(time.Second)
}
2.1.2 Channel通道
Go提倡通过 通信 共享内存
通过make可以创建无缓冲通道 make(chan int)或者有缓冲通道 make(chan int, 2)
下面创建一个A、B协程,前者负责生成数字到无缓冲通道中,后者负责从无缓冲通道取出数字,做平方后放入新的有缓冲通道中。
package main
func main() {
src := make(chan int)
dest := make(chan int, 3)
// A协程
go func() {
defer close(src) // defer延迟执行,先defer的最后执行(栈结构)
for i := 0; i < 10; i++ {
src <- i
}
}()
// B协程
go func() {
defer close(dest)
for i := range src {
dest <- i << 2 // i^2
}
}()
for j := range dest {
println(j)
}
}
2.1.3 并发安全 Lock
如果共享内存,会存在并发安全问题。
用两个协程进行测试,分别执行加锁和不加锁的函数,对比其差异:
package main
import (
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func add() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func main() {
// 分别创建5个协程,分别+2000,总体+10000
x = 0
for i := 0; i < 5; i++ {
go add()
}
time.Sleep(time.Second)
println("NoLock: ", x) // 6225,执行结果不一致
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("WithLock: ", x) // 10000
}
2.1.4 WaitGroup
通过WaitGroup内置的计数器,可以实现阻塞等功能,如下所示:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup // 定义一个wg
wg.Add(5) // 设置阈值为5
for i := 0; i < 5; i++ {
go func(j int) { // 使用go标识符创建协程
defer wg.Done() // Done一次计数器++
fmt.Println(j)
}(i)
}
wg.Wait() // 计数器到5继续执行这一步
}
2.2 依赖
2.2.1 管理
简单来说:站在巨人的肩膀上,因为工程项目不可能基于简单的编码搭建
演进历史:GOPATH -> Go Vendor -> Go Module,为了解决不同环境依赖版本不同的问题,以及控制依赖库的版本。
三要素:1. 配置文件,描述依赖(go.mod)2. 中心仓库管理依赖库(Proxy) 3. 本地工具(go get/mod)
2.2.2 分发
如果直接从GitHub等第三方构建项目,无法保证构建的稳定性、可用性(软件可以被增删改),且增加了第三方平台压力。
所以,使用Proxy保证稳定性。例如公共GOPROXY是一个集中式的存储库,全球各地的Golang开发者都可以使用它。它缓存了大量开源的Go模块,这些模块可以从第三方公开访问的VCS项目存储库中获得。
2.2.3 工具
go get
go mod init/download/tidy