Goroutine


在 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.WaitGroupDone方法。

因此,我们可以使用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()函数或环境变量来改变设定。


展开阅读全文