go 语言 sync.Wait的使用

如何让主 goroutine 在所有其他 goroutine 都运行完后,再退出

sleep

通过 sleep 让主 goroutine 在所有子goroutine运行结束后,再退出

func usingSleep() {
	for i := 0; i < 10; i++ {
		go fmt.Println(i)
	}
	time.Sleep(time.Second)
}

缺点:无法判断 for 循环到底需要多长时间,导致估计sleep的时间要么不够,要么太长

缓冲通道

通过缓冲通道让主 goroutine 在所有子goroutine运行结束后,再退出

func usingChannel() {
	c := make(chan struct{}, 10)
	for i := 0; i < 10; i++ {
		go func(i int) {
			fmt.Println(i)
			c <- struct{}{}
		}(i)
	}

	for i := 0; i < 10; i++ {
		<-c
	}

}

缺点:

  • channel应该被用在goroutine间的通信
  • 如果子goroutine太多,使用通道也会消耗很多资源

sync.WaitGroup

使用 sync.WaitGroup()控制主 goroutine 在所有子goroutine运行结束后,再退出

func waitGroup() {
	wg := sync.WaitGroup{}

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			fmt.Println(i)
		}(i)
	}

	wg.Wait() // 一直阻塞主goroutine,直到wg为零

	// output
	// 乱序输出,并不是按1,2,3,4...
	// 2
	// 4
	// 7
	// 3
	// 1
	// 0
	// 9
	// 5
	// 8
	// 6
}
  • Wait() 方法的功能是,阻塞当前的 goroutine,直到其所属值中的计数器归零

  • 不要把增加其计数器值的操作和调用其Wait方法的代码,放在不同的 goroutine 中执行,如果同时启用的两个 goroutine ,分别调用这两个方法(add 和 wait),那么就有可能会让这里的Add方法抛出一个 panic。最好用“先统一Add,再并发Done,最后Wait”这种标准方式,来使用WaitGroup值

  • 计数器不能为负值,发生为负的情况是:不适当地调用Done方法和Add方法都会如此。

  • Done()是Add(-1)的别名

  • 计数不为0, 阻塞Wait()的运行

WaitGroup对象不是一个引用类型

在通过函数传值的时候需要使用地址,不然会出现 fatal error: all goroutines are asleep - deadlock!

func passWGByPointer() {
	wg := sync.WaitGroup{}

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go wgDone(i, &wg)
	}
	wg.Wait()

}
func wgDone(i int, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Println(i)
}