在 Go 中要让指定的流程并行执行非常简单,只需要将流程写在函数中,并在函数加个go
就可以了,这样我们称之为启动一个 Goroutine。
使用 Gorutine
先来看个没有启用 Goroutine,却要写个龟兔赛跑游戏的例子,你可能是这么写的:
package main
import (
"fmt"
"math/rand"
"time"
)
func random(min, max int) int {
rand.Seed(time.Now().Unix())
return rand.Intn(max-min) + min
}
func main() {
flags := [...]bool{true, false}
totalStep := 10
tortoiseStep := 0
hareStep := 0
fmt.Println("龟兔赛跑开始...")
for tortoiseStep < totalStep && hareStep < totalStep {
tortoiseStep++
fmt.Printf("乌龟跑了 %d 步...\n", tortoiseStep)
isHareSleep := flags[random(1, 10)%2]
if isHareSleep {
fmt.Println("兔子睡着了zzzz")
} else {
hareStep += 2
fmt.Printf("兔子跑了 %d 步...\n", hareStep)
}
}
}
由于程序只有一个流程,所以只能将乌龟与兔子的行为混杂在这个流程中编写,而且为什么每次都先递增乌龟再递增兔子步数呢?这样对兔子很不公平啊!如果可以编写程序再启动两个流程,一个是乌龟流程,一个兔子流程,程序逻辑会比较清楚。
你可以将乌龟的流程与兔子的流程分别写在一个函数中,并用go
启动执行:
package main
import (
"fmt"
"math/rand"
"time"
)
func random(min, max int) int {
rand.Seed(time.Now().Unix())
return rand.Intn(max-min) + min
}
func tortoise(totalStep int) {
for step := 1; step <= totalStep; step++ {
fmt.Printf("乌龟跑了 %d 步...\n", step)
}
}
func hare(totalStep int) {
flags := [...]bool{true, false}
step := 0
for step < totalStep {
isHareSleep := flags[random(1, 10)%2]
if isHareSleep {
fmt.Println("兔子睡着了zzzz")
} else {
step += 2
fmt.Printf("兔子跑了 %d 步...\n", step)
}
}
}
func main() {
totalStep := 10
go tortoise(totalStep)
go hare(totalStep)
time.Sleep(5 * time.Second) // 给予时间等待 Goroutine 完成
}
现在乌龟的流程与兔子的流程都清楚多了,程序的最后使用time.Sleep()
让主流程沉睡了五秒钟,这是因为主流程一结束,所有的 Goroutine 就会停止。
使用 sync.WaitGroup
有没有办法知道 Goroutine 执行结束呢?实际上没有任何方法可以得知,除非你主动设计一种机制,可以在 Goroutine 结束时执行通知,使用 Channel 是一种方式,这在之后的文件再说明,这边先说明另一种方式,也就是使用sync.WaitGroup
。
sync.WaitGroup
可以用来等待一组 Goroutine 的完成,主流程中创建sync.WaitGroup
,并透过Add
告知要等待的 Goroutine 数量,并使用Wait
等待 Goroutine 结束,而每个 Goroutine 结束前,必须执行sync.WaitGroup
的Done
方法。
因此,我们可以使用sync.WaitGroup
来改写以上的范例:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func random(min, max int) int {
rand.Seed(time.Now().Unix())
return rand.Intn(max-min) + min
}
func tortoise(totalStep int, wg *sync.WaitGroup) {
defer wg.Done()
for step := 1; step <= totalStep; step++ {
fmt.Printf("乌龟跑了 %d 步...\n", step)
}
}
func hare(totalStep int, wg *sync.WaitGroup) {
defer wg.Done()
flags := [...]bool{true, false}
step := 0
for step < totalStep {
isHareSleep := flags[random(1, 10)%2]
if isHareSleep {
fmt.Println("兔子睡着了zzzz")
} else {
step += 2
fmt.Printf("兔子跑了 %d 步...\n", step)
}
}
}
func main() {
wg := new(sync.WaitGroup)
wg.Add(2)
totalStep := 10
go tortoise(totalStep, wg)
go hare(totalStep, wg)
wg.Wait()
}
有个runtime.GOMAXPROCS()
函数,可以设定 Go 同时间能使用的 CPU 数量,它会返回上一次设定的数字,如果传入小于 1 的值,不会改变任何设定,因此,可以使用runtime.GOMAXPROCS(0)
知道目前的设定值。想在执行时期得知可用的 CPU 数量,可以使用runtime.NumCPU()
函数,因此,为了确保 Go 会使用全部的 CPU 来运行,可以这么编写:
runtime.GOMAXPROCS(runtime.NumCPU())
除了透过runtime.GOMAXPROCS()
设定之外,也可以透过环境变量GOMAXPROCS
来设置,实际上,Go 1.5 已经默认会使用所有的 CPU 核心,不过,仍可以透过runtime.GOMAXPROCS()
函数或环境变量来改变设定。