
Go语言的并发,就不得不提goroutine,其作为Go语言的一大特色,在日常开发中使用很多
这里就不对goroutine进行描述了,直接进入正文
go并发控制方法主要有:
- 公共变量
- channel
- WaitGroup
- context
公共变量
这是并发控制最简单的实现方式
- 声明一个公共变量
- 所有子goroutine共享这个变量,并不断轮询这个变量并检查是否有更新
- 在主进程中变更该公共变量
- 子goroutine检测到公共变量更新,执行相应的逻辑
package main
import (
"fmt"
"time"
)
func main() {
// 公共变量
state := true
go func(){
for state {
println("goroutine A running")
time.Sleep(1 * time.Second)
}
println("goroutine A exit")
}()
go func() {
for state {
println("goroutine B running")
time.Sleep(1 * time.Second)
}
println("goroutine B exit")
}()
time.Sleep(2 * time.Second)
state = false
time.Sleep(2 * time.Second)
fmt.Println("main func exit")
}输出结果
goroutine B running
goroutine A running
goroutine A running
goroutine B running
goroutine B running
goroutine A running
goroutine A exit
goroutine B exit
main func exit这种实现方式比较简单,但如果逻辑过于复杂,则变量不容易控制
channel
channel是goroutine之间主要的通讯方式,一般会和select搭配使用
channel在这里也不做过多描述,大致处理过程为:
- 声明一个可以判断结束的chan
- 在goroutine中,使用select判断chan接收到的值。如果接收到结束,就可以走结束逻辑。如果没有,那么就会执行预设的逻辑,或者default
- 主程序发送了结束的指令后
- 子goroutine接到结束指令,进行结束逻辑
package main
import (
"fmt"
"time"
)
func main() {
// 初始化一个接收bool的channel
c := make(chan bool)
go func() {
for {
select {
case <- c:
fmt.Println("goroutine exit")
return
default:
fmt.Println("goroutine running.")
}
time.Sleep(100 * time.Millisecond)
}
}()
time.Sleep(1 * time.Second)
c <- false
fmt.Println("main fun exit")
}输出结果
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine exit
main fun exit这种方法比较优雅,但稍微复杂
如果要控制多个goroutine,或者goroutine嵌套,就比较麻烦
WaitGroup
Go语言提供同步包sync
sync包同步提供基本的同步原语,如互斥锁。除了Once和WaitGroup类型之外,大多数类型都是供低级库例程使用的。通过Channel和沟通可以更好地完成更高级别的同步。并且此包中的值在使用过后不要拷贝。
Sync.WaitGroup是一种实现并发控制方式,WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。
- Add(n) 把计数器设置为n
- Done() 每次把计数器-1
- wait() 会阻塞代码的运行,直到计数器地值减为0
package main
import (
"fmt"
"sync"
"time"
)
func main() {
fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
//定义一个WaitGroup
var synWait sync.WaitGroup
//计数器设置为2
synWait.Add(2)
go func() {
time.Sleep(1 * time.Second)
fmt.Println("goroutine A finish |", time.Now().Format("2006-01-02 15:04:05"))
//计数器减1
synWait.Done()
}()
go func() {
time.Sleep(3 * time.Second)
fmt.Println("goroutine B finish |", time.Now().Format("2006-01-02 15:04:05"))
//计数器减1
synWait.Done()
}()
//会阻塞代码的运行,直到计数器地值减为0。
synWait.Wait()
fmt.Println("main fun exit |", time.Now().Format("2006-01-02 15:04:05"))
}输出结果
2019-12-28 01:27:02
goroutine A finish | 2019-12-28 01:27:03
goroutine B finish | 2019-12-28 01:27:05
main fun exit | 2019-12-28 01:27:05可以看出A在1秒后输出,B在3秒后输出
这种方法适合多个goroutine做同一个,或者差不多事情的时候
因为每个goroutine做的都是这件事情的一部分,只有全部的goroutine都完成,这件事情才算是完成,这是等待的方式。WaitGroup相对于channel并发控制方式比较轻巧
context
应用场景:在 Go http 包的 Server 中,每个Request都需要开启一个goroutine做一些事情,这些goroutine又可能会开启其他的goroutine
所以我们需要一种可以跟踪goroutine的方案,才可以达到控制他们的目的,这就是Go语言为我们提供的Context,称之为上下文
控制并发的实现方式:
- context.Background():返回一个空的Context,这个空的Context一般用于整个Context树的根节点
- context.WithCancel(context.Background()),创建一个可取消的子Context,然后当作参数传给goroutine使用,这样就可以使用这个子Context跟踪这个goroutine
- 在goroutine中,使用select调用<-ctx.Done()判断是否要结束,如果接收到值的话,就可以返回结束goroutine了。如果接收不到,就会继续进行监控
- cancel(),取消函数(context.WithCancel()返回的第二个参数,名字和声明的名字一致)。作用是给goroutine发送结束指令
package main
import (
"fmt"
"time"
"context"
)
func main() {
//创建一个可取消子context,context.Background():返回一个空的Context,这个空的Context一般用于整个Context树的根节点
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
//使用select调用<-ctx.Done()判断是否要结束
case <-ctx.Done():
fmt.Println("goroutine exit")
return
default:
fmt.Println("goroutine running.")
time.Sleep(100 * time.Millisecond)
}
}
}(ctx)
time.Sleep(1 * time.Second)
//取消context
cancel()
time.Sleep(1 * time.Second)
fmt.Println("main fun exit")
}输出结果
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine running.
goroutine running.
main fun exit如果想控制多个goroutine ,也很简单
package main
import (
"fmt"
"time"
"context"
)
func main() {
//创建一个可取消子context,context.Background():返回一个空的Context,这个空的Context一般用于整个Context树的根节点。
ctxA, cancelA := context.WithCancel(context.Background())
ctxB, cancelB := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
//使用select调用<-ctx.Done()判断是否要结束
case <- ctx.Done():
fmt.Println("goroutine A exit")
return
default:
fmt.Println("goroutine A running.")
time.Sleep(200 * time.Millisecond)
}
}
}(ctxA)
go func(ctx context.Context) {
for {
select {
//使用select调用<-ctx.Done()判断是否要结束
case <- ctx.Done():
fmt.Println("goroutine B exit")
return
default:
fmt.Println("goroutine B running.")
time.Sleep(200 * time.Millisecond)
}
}
}(ctxA)
go func(ctx context.Context) {
for {
select {
//使用select调用<-ctx.Done()判断是否要结束
case <- ctx.Done():
fmt.Println("goroutine C exit")
return
default:
fmt.Println("goroutine C running.")
time.Sleep(200 * time.Millisecond)
}
}
}(ctxB)
time.Sleep(1 * time.Second)
//取消context
cancelA()
cancelB()
time.Sleep(1 * time.Second)
fmt.Println("main fun exit")
}输出结果:
goroutine A running.
goroutine C running.
goroutine B running.
goroutine A running.
goroutine C running.
goroutine B running.
goroutine A running.
goroutine C running.
goroutine B running.
goroutine A running.
goroutine C running.
goroutine B running.
goroutine A running.
goroutine C running.
goroutine B running.
goroutine A exit
goroutine C exit
goroutine B exit
main fun exit至于context的具体用法,请参考深度解密Go语言之context
参考文档
Go goroutine理解
Go channel 实现原理分析
深度解密Go语言之context
Go 并发控制
GO语言并发编--sync包之WaitGroup的使用
Go Context的踩坑经历
版权属于:谁把年华错落成诗 所有,转载请注明出处!
本文链接:https://blog.pomears.com/archives/52.html
如果博客部分链接出现 404,请留言或者联系博主修复。