恭喜你学习围棋。作为新手,很高兴了解并发以及它与并行的不同之处。
并发
并发就像一个杂耍者用一只手在空中玩几个球。不管他玩多少球,任何时候只有一个球碰到他的手。
并行性
当杂耍者开始用另一只手并行地杂耍更多球时,我们有两个并发的进程同时运行。
Goroutines 很棒,因为它们既是并发又是自动并行,这取决于可用的计算核心和设置的GOMAXPROCS 变量。
单手杂耍者
回到单手、单核、并发的杂耍者。想象一下,他以main 的程序处理三个分别名为“hello”、“world”和“mars”的球。
var balls = []string{"hello", "world", "mars"}
func main() {
go say(balls[0])
go say(balls[1])
go say(balls[2])
}
或者更恰当地说,
func main() {
for _, ball := range balls {
go say(ball)
}
}
一旦三个球依次被抛向空中,杂耍者就会立即收回手。也就是说,main 例程在投出的第一个球甚至可以落在他的手上之前就退出了。可惜,球只是掉在地上。糟糕的表演。
为了让球回到他的手中,杂耍者必须确保他等待他们。这意味着他的手需要能够跟踪和计算他扔的球,并了解每个球何时落地。
最直接的方法是使用sync.WaitGroup:
import (
"fmt"
"time"
"sync"
)
var balls = []string{"hello", "world", "mars"}
var wg sync.WaitGroup
func main() {
for _, ball := range balls {
// One ball thrown
wg.Add(1)
go func(b string) {
// Signals the group that this routine is done.
defer wg.Done()
// each ball hovers for 1 second
time.Sleep(time.Duration(1) * time.Second)
fmt.Println(b)
// wg.Done() is called before goroutine exits
}(ball)
}
// The juggler can do whatever he pleases while the
// balls are hanging in the air.
// This hand will come back to grab the balls after 1s.
wg.Wait()
}
WaitGroup 很简单。当生成一个 goroutine 时,使用WaitGroup.Add(1) 添加一个“积压计数器”并调用WaitGroup.Done() 来减少计数器。一旦 backlog 变为 0,就意味着所有的 goroutine 都完成了,WaitGroup 应该停止等待(并抓住球!)。
虽然使用通道进行同步很好,但建议酌情使用可用的并发工具,尤其是在使用通道使代码更复杂且难以理解时。