1.并发编程
- 从多线程程序运行的视角来看,并发是指多线程在一个核CPU上的运行,通过时间片的切换来实现同时运行的效果。
- 并行是指利用多核CPU在多线程程序上运行。
- 并行可以理解为实现并发的一个手段。
- GO语言实现了一个并发性能极高的调度模型,通过高效调度,可以最大限度地利用计算资源,充分发挥多核计算机的优势:“GO语言就是为实现并发而生的”。
1.1 Goroutine 协程
- 协程: 用户态,轻量级线程,栈KB级别
- 线程: 内核态,线程可以跑多个协程,栈MB级别
package main
import (
"fmt"
"time"
)
func hello(i int) {
println("hello groutine" + fmt.Sprint(i)) // fmt.Sprint(i)将整数转换为字符串
}
func main() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i) // 立即执行匿名函数,并将当前的循环变量i作为参数传递
}
time.Sleep(time.Second)
}
- go关键字可以简单地开启一个协程(goroutine)。
- 输出是乱序,这是正常的,不同协程的运行速度是不一样的。
1.2 CSP
通过通信共享内存
- GO提倡通过通信来共享内存(而不是通过共享内存而实现通信)
- channel通道将协程之间做了一个连接。是让一个协程发送消息到另一个协程的通信机制。
通过共享内存实现通信
- 通过临时缓存对内存进行加锁
- 该机制在一定程度上会影响程序的性能
1.3 Channel
- 类型:引用类型
- 创建:通过make关键字
make(chan 元素类型,[缓冲区大小])
make(chan int) // 无缓冲通道
make(chan int,2) // 有缓冲通道
- 无缓冲通道会使发送的中心和接收的中心同步化,所以也被称为同步通道
- 解决同步问题的方式是采用带有缓冲区的有缓冲通道
package main
func main() {
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)
}
}
- 在这段代码中,定义了一个无缓冲的通道src,和有缓冲的通道dest。
- 在第一个子进程中生产数字,第二个子进程进行平方操作,最后主进程进行打操作。
- 第二个子进程采用有缓存是因为消费者的打印速度可能要慢于生产者,采用有缓冲的通道可以提高效率。
- 在Go语言中,
defer语句用于确保在函数返回之前执行特定的操作,比如关闭文件或通道。在这个例子中,defer close(src)确保在第一个goroutine完成发送所有值之后关闭src通道。
1.4 并发安全Lock
package main
import (
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x++
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x++
}
}
func main() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("withoutLock", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("withLock", x)
}
- 运行结果所示,不加锁的协程会出现丢数据的问题,在工程中要尽量避免并发带来的此类问题
1.5 WithGroup
- GO语言可以设置WaitGroup实现并发任务的同步
- WaitGroup可以实现3个方法,分别是Add(delta int),Done()和Wait()
Add(delta int):计数器+delta Done():计数器-1 Wait():阻塞直到计数器为0
2. 依赖管理
2.1 Go依赖管理演进
- GOPATH->GO Vendor->GO Module
- 不同环境依赖的版本不同
- 控制依赖库的版本
2.1.1 GOPATH
- 弊端:无法实现packge多版本控制
2.1.2 Go Vendor
- 弊端:GO Vendor依赖于项目源码,并不能很清晰地标识版本的概念。
2.1.3 Go Module
- 通过go.mod文件管理依赖包版本
- 通过go get/go mod 指令工具管理依赖包
2.2 依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
2.3 依赖配置
2.3.1 依赖配置-go.mod
module workspace // 模块路径
go 1.23.2 // 原生库
require(
)
// 单元依赖
2.3.2 依赖配置-version
2.3.3 依赖配置-indirect
- 间接依赖。