并发与同步
Go 协程(Goroutine)
并行强调多核,多线程程序在多个核上运行,而并发狭义是指利用时间片等操作使多个线程在一个核的cpu上运行,而并行也可以叫做广义的并发,Go 可以充分发挥多核优势,高效运行。
Go 协程(Goroutine)在用户态,是轻量级线程,栈是 KB 级别,而线程是内核态,一个线程可以跑多个协程,线程的栈是 MB 级别。
启动 Go 协程的方法是在函数调用之前加一个 go
关键字,下面是一个例子。
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()
}
ManyGo
函数是使用 sync.WaitGroup
实现协程并发的示例。
首先创建了一个 sync.WaitGroup
类型的变量 wg
。WaitGroup
用于等待一组协程完成执行。
然后,通过 for
循环创建了 5 个协程。在循环内部,首先调用 wg.Add(1)
来增加 WaitGroup
的计数器,表示有一个协程正在执行。然后使用 go
关键字开启一个匿名函数作为协程。
在匿名函数中,我们使用 defer wg.Done()
来在函数执行完毕后减少 WaitGroup
的计数器,表示一个协程已经完成执行。然后调用 hello(j)
函数,将传递进来的 j
参数作为参数传递给 hello
函数,这个匿名函数的实际参数是循环变量 i
。
通过使用 sync.WaitGroup
,可以确保在所有协程执行完毕之前,程序会一直等待,调用 wg.Wait()
来阻塞主程序,直到所有协程都完成执行。
这样,当调用 ManyGo()
函数时,5 个协程并发执行 hello
函数,每个协程拥有不同的 j
值,并且会等待所有协程执行完毕后才结束。
通道(Channel)
Go 语言通过通信共享内存,同时也保留了通过共享内存实现通信的方法,不过使用共享内存需要使用锁(Lock)。
两个协程通过通信共享内存,需要使用通道,Go 可以创建有缓冲通道和无缓冲通道。make(chan 元素类型, [缓冲大小])
,下面是一个例子。
func CalSquare() {
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)
}
}
CalSquare
函数是一个使用通道实现的示例,用于计算一组数字的平方值。
首先创建了两个通道 src
和 dest
。其中 src
通道用来发送原始数字,dest
通道用来接收计算后的平方值。注意,dest
通道的缓冲区大小设置为 3,表示可以同时存储最多 3 个平方值。
然后,通过 go
关键字开启了两个匿名函数作为协程。第一个匿名函数使用循环将数字从 0 到 9 发送到 src
通道中,并在发送完毕后关闭 src
通道。第二个匿名函数使用 range
循环从 src
通道中接收数字,并计算它们的平方值,然后将结果发送到 dest
通道中,并在处理完所有数字后关闭 dest
通道。
接下来,在主程序中使用 range
循环从 dest
通道中接收计算后的平方值,并将其打印输出。
这样,当调用 CalSquare()
函数时,程序并发地计算一组数字的平方值,并按顺序输出结果。由于 dest
通道设置了缓冲区,可以同时接收多个平方值,使得计算和接收操作可以并行进行。
同步(Sync)
为了保证并发安全需要做到同步,有两个方式。
-
使用 Lock 锁
在 Go 语言中,使用
sync.Mutex
类型的变量可以创建一个互斥锁。通过调用锁的Lock
方法可以获取锁,防止其他 Goroutine 进入被保护的代码区域。在代码执行完毕后,通过调用锁的Unlock
方法释放锁,使得其他 Goroutine 可以获取锁并进入该区域。 -
使用 WaitGroup
通过使用
sync.WaitGroup
,可以确保在所有协程执行完毕之前,程序会一直等待,详见协程部分的函数例子。var wg sync.WaitGroup
创建计数器变量wg.Add(delta int)
计数器增加 delta,表示增加 delta 个协程wg.Done()
计数器减 1wg.Wait()
阻塞直到计数器为 0
Go module
首先打开 module
模式,这会摒弃掉 GOPATH
和 vendor
,从 1.13 开始,默认开启 module
模式
go env -w GO111MODULE=on
然后设置GOPROXY
go env -w GOPROXY=https://goproxy.cn,direct
go mod 相关命令
go mod
The commands are:
download download modules to local cache (下载依赖的module到本地cache)
edit edit go.mod from tools or scripts (编辑go.mod文件)
graph print module requirement graph (打印模块依赖图)
init initialize new module in current directory (在当前文件夹下初始化一个新的module, 并创建go.mod文件)
tidy add missing and remove unused modules (增加缺失module,去掉无用module)
vendor make vendored copy of dependencies (复制依赖到vendor)
verify verify dependencies have expected content (校验依赖内容)
why explain why packages or modules are needed (解释为什么需要依赖或模块)