Goroutine (并发)

    首先我们看一个例子:

    1. package main
    2. import (
    3. "log"
    4. "time"
    5. )
    6. func doSomething(id int) {
    7. log.Printf("before do job:(%d) \n", id)
    8. time.Sleep(3 * time.Second)
    9. log.Printf("after do job:(%d) \n", id)
    10. }
    11. func main() {
    12. doSomething(1)
    13. doSomething(2)
    14. doSomething(3)
    15. }

    输出结果为:

    1. 2018/03/16 12:13:20 before do job:(1)
    2. 2018/03/16 12:13:23 after do job:(1)
    3. 2018/03/16 12:13:23 before do job:(2)
    4. 2018/03/16 12:13:26 after do job:(2)
    5. 2018/03/16 12:13:26 before do job:(3)
    6. 2018/03/16 12:13:29 after do job:(3)

    可以看到执行完结果总共耗时 9 秒,每个任务是阻塞的。

    我们可以使用 goroutine 并发执行任务,从而整体加快速度,下面是使用 goroutine 改进的代码:

    这是因为我们的 main() 函数其实也是在一个 goroutine 中执行,但是 main() 执行完毕后,其他三个 goroutine 还没开始执行,所以就无法看到输出结果。

    为了看到输出结果,我们可以使用 time.Sleep() 方法让 main() 函数延迟结束,例如:

    1. package main
    2. import (
    3. "time"
    4. func doSomething(id int) {
    5. log.Printf("before do job:(%d) \n", id)
    6. time.Sleep(3 * time.Second)
    7. log.Printf("after do job:(%d) \n", id)
    8. }
    9. func main() {
    10. go doSomething(1)
    11. go doSomething(2)
    12. go doSomething(3)
    13. time.Sleep(4 * time.Second)
    14. }

    输出结果为:

    1. 2018/03/16 12:24:23 before do job:(2)
    2. 2018/03/16 12:24:23 before do job:(1)
    3. 2018/03/16 12:24:23 before do job:(3)
    4. 2018/03/16 12:24:26 after do job:(3)
    5. 2018/03/16 12:24:26 after do job:(2)
    6. 2018/03/16 12:24:26 after do job:(1)

    可以看到,执行完所有任务从原本的 9 秒下降到 3 秒,大大提高了我们的效率,根据打印输出结果还可以看出:

    • 多个 goroutine 的执行是随机。
    • 对于 IO 密集型任务特别有效,比如文件,网络读写。

    运行代码输出结果为:

    1. 2018/03/16 13:56:09 before do job:(1)
    2. 2018/03/16 13:56:09 before do job:(3)
    3. 2018/03/16 13:56:09 before do job:(2)
    4. 2018/03/16 13:56:12 after do job:(1)
    5. 2018/03/16 13:56:12 after do job:(2)
    6. 2018/03/16 13:56:12 after do job:(3)
    7. 2018/03/16 13:56:12 finish all jobs

    我们一起来猜猜,下面一段代码运行结果是什么?

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. func main() {
    6. for i := 0; i < 3; i++ {
    7. go func() {
    8. fmt.Println(i)
    9. }()
    10. }
    11. time.Sleep(1 * time.Second)
    12. }

    运行代码,输出结果为:

    我们想要的是随机打印 0,1,2,但实际输出结果和我们预期不一致,这是原因:

    1. 所有 goroutine 代码片段中的 i 是同一个变量,待循环结束的时候,它的值为 3
    2. main() 循环结束后才开始并发执行的新生成的 goroutine。
    1. package main
    2. import (
    3. "fmt"
    4. "time"
    5. )
    6. func main() {
    7. for i := 0; i < 3; i++ {
    8. go func(v int) {
    9. fmt.Println(v)
    10. }(i)
    11. }
    12. time.Sleep(1 * time.Second)

    我们可以通过方法传参的方式,将 i 的值拷贝到新的变量 v 中,而在每一个 goroutine 都对应了一个属于自己作用域的 变量,所以最终打印结果为随机的 0,1,2