Golang中WaitGroup使用的一点坑 4年前

Golang中WaitGroup使用的一点坑

Golang 中的 WaitGroup 一直是同步 goroutine 的推荐实践。自己用了两年多也没遇到过什么问题。直到一天午睡后,同事扔过来一段奇怪的代码:

坑1

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

package main

import (

"log"

"sync"

)

func main() {

wg := sync.WaitGroup{}

for i := 0; i < 5; i++ {

go func(wg sync.WaitGroup, i int) {

wg.Add(1)

log.Printf("i:%d", i)

wg.Done()

}(wg, i)

}

wg.Wait()

log.Println("exit")

}

撇了一眼,觉得没什么问题。然而,它的运行结果是这样:

1

2

3

2016/11/27 15:12:36 exit

[Finished in 0.7s]

或这样:

1

2

3

4

2016/11/27 15:21:51 i:2

2016/11/27 15:21:51 exit

[Finished in 0.8s]

或这样:

1

2

3

4

5

2016/11/27 15:22:51 i:3

2016/11/27 15:22:51 i:2

2016/11/27 15:22:51 exit

[Finished in 0.8s]

一度让我以为手上的 mac 也没睡醒……
这个问题如果理解了 WaitGroup 的设计目的就非常容易 fix 啦。因为 WaitGroup 同步的是 goroutine, 而上面的代码却在 goroutine 中进行 Add(1) 操作。因此,可能在这些 goroutine 还没来得及 Add(1) 已经执行 Wait 操作了。

于是代码改成了这样:

坑2

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

package main

import (

"log"

"sync"

)

func main() {

wg := sync.WaitGroup{}

for i := 0; i < 5; i++ {

wg.Add(1)

go func(wg sync.WaitGroup, i int) {

log.Printf("i:%d", i)

wg.Done()

}(wg, i)

}

wg.Wait()

log.Println("exit")

}

然而,mac 又睡了过去,而且是睡死了过去:

1

2

3

4

5

6

7

2016/11/27 15:25:16 i:1

2016/11/27 15:25:16 i:2

2016/11/27 15:25:16 i:4

2016/11/27 15:25:16 i:0

2016/11/27 15:25:16 i:3

fatal error: all goroutines are asleep - deadlock!

wg 给拷贝传递到了 goroutine 中,导致只有 Add 操作,其实 Done操作是在 wg 的副本执行的。因此 Wait 就死锁了。于是代码改成了这样:

填坑

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

package main

import (

"log"

"sync"

)

func main() {

wg := &sync.WaitGroup{}

for i := 0; i < 5; i++ {

wg.Add(1)

go func(wg *sync.WaitGroup, i int) {

log.Printf("i:%d", i)

wg.Done()

}(wg, i)

}

wg.Wait()

log.Println("exit")

}

至此,午睡终于睡醒了。Sigh…

image
凉子喵
游荡的孤高灵魂不需要羁绊之处。
6
发布数
2
关注者
11841
累计阅读

热门教程文档

Gin
17小节
10.x
88小节
HTML
32小节
Java
12小节
Kotlin
68小节
广告