这是我参与「第五届青训营」伴学笔记创作活动的第2天
前言
当谈到Go语言中的并发性时,Go语言的一个特性goroutine在日常开发中被大量使用。
启动goroutine非常简单,只需在函数之前添加关键字go。因为每个goroutine都是独立运行的,所以它的退出是由它自己决定的,除非主程序终止或崩溃。
那么,如何控制goroutine或告诉goroutine停止运行呢?
解决办法很简单。找到一种与goroutine通信的方法,并让他们知道goroutine何时完成。当goroutine完成时,可以通知另一个goroutine或主程序。
channel
channel是goroutine之间的主要通信方法,通常与select结合使用。
- 宣布一个stopchan。
2、在goroutine中,使用select来判断'stop'是否可以接收到值,如果可以接收到,就意味着可以退出停止;如果没有接收到,则执行'default'中的逻辑。直到收到“停止”通知为止。
3、主程序发送'stop< -true'结束指令。
4,子goroutine接收到结束指令case <-stop exit return。
示例
package main
import (
"fmt"
"time"
)
func main() {
stop := make(chan bool)
go func() {
for {
select {
case <-stop:
fmt.Println("goroutine exit")
return
default:
fmt.Println("goroutine running")
time.Sleep(1 * time.Second)
}
}
}()
time.Sleep(2 * time.Second)
stop <- true
time.Sleep(2 * time.Second)
fmt.Println("main fun exit")
}
输出
goroutine running
goroutine running
goroutine running
goroutine exit
main fun exit
这种select+chan是一种比较优雅的并发控制方式,但也有局限性,如多个goroutine 需要结束,以及嵌套goroutine 的场景。
WaitGroup
Go语言提供了同步包(sync),源代码(src/sync/waitgroup.go)。 Sync包提供了基本的同步原语,比如互斥。除了Once和WaitGroup类型外,大多数类型都由低级库例程使用。通过通道和通信可以更好地实现更高级别的同步。并且这个包中的值在使用后不应该被复制。
同步。WaitGroup是一种实现并发控制的方法。WaitGroup对象内部有一个计数器,从0开始。它有三个方法:'Add(), Done(), Wait()'来控制计数器的数量。
- 'Add(n)'设置计数器为n '。
- 'Done()'每次将计数器更改为-1。
- 'wait()'阻塞代码,直到计数器减少到零。 例子
package main
import (
"fmt"
"sync"
"time"
)
func main() {
//定义一个WaitGroup
var wg sync.WaitGroup
//计数器设置为2
wg.Add(2)
go func() {
time.Sleep(2 * time.Second)
fmt.Println("goroutineA finish")
//计数器减1
wg.Done()
}()
go func() {
time.Sleep(2 * time.Second)
fmt.Println("goroutineB finish")
//计数器减1
wg.Done()
}()
//会阻塞代码的运行,直到计数器地值减为0。
wg.Wait()
time.Sleep(2 * time.Second)
fmt.Println("main fun exit")
}
当有多个goroutine一起工作来做某件事时,这种控制并发性的方式非常有用,因为每个goroutine都在做某件事的一部分,直到所有goroutine都完成它,它才会完成,这就是等待的方式。WaitGroup比通道并发控制更轻。
总结
在Golang中并没有好的或坏的并发行为控制模型,只是针对不同场景的适当解决方案。在实际项目中,经常混合使用多种方法。
- WaitGroup:多个goroutine的任务相互依赖或拼接。
- channel:取消goroutine。多路程序数据传输;通道可以完成WaitGroup的工作,但增加了代码逻辑复杂性