并发同步(二)—— 等待组

一 等待组 sync.WaitGroup

sync.WaitGroup类型的值也是并发安全的,该类型结构体中内内部拥有一个计数器,计数器的值可以通过方法调用实现计数器的增加和减少 。

当我们添加了 N 个并发任务进行工作时,就将等待组的计数器值增加 N。每个任务完成时,这个值减1。 同时,在另外一个 goroutine 中等待这个等待组的计数器值为 0 时, 表示所有任务己经完成。

等待组常用方法:

  • (wg *WaitGroup) Add(delta int) 等待组计数器+1,该方法也可以传入负值让等待计数减少,切记不能减少为负数,会引发崩溃
  • (wg *WaitGroup) Done() 等待组计数器-1,等同于Add传入负值
  • (wg *WaitGroup) Wait() 等待组计数器!=0时阻塞,直到为0

应用场景:WaitGroup一般用于协调多个goroutine运行。

简单示例:

package main

import (
	"fmt"
	"net/http"
	"sync"
)

func main() {

	var wg sync.WaitGroup					// 声明一个等待组

	var urls = []string{					
		"https://www.baidu.com/",
		"https://www.163.com/",
		"https://www.weibo.com/",
	}

	for _, url := range urls {
		wg.Add(1)						// 每个任务开始,等待组+1
		go func(url string) {
			defer wg.Done()
			_, err := http.Get(url)		// 执行访问
			fmt.Println(url, err)
		}(url)
	}

	wg.Wait()						// 等待所有任务完成
	fmt.Println("over")

}

上述案例可以使用channel方式,每个go协程执行时,channel传递完成信号,但是使用通道的方式明显过重。

贴士:有了等待组,我们就不需要再在main函数中使用 time.Sleep()方法来模拟等待协程运行结束了。

二 等待组与锁配合使用示例

开启多个协程对共享内存产生竞争,单独使用等待组不能解决问题,如下所示:

package main

import (
	"fmt"
	"sync"
)

func main() {

	var wg sync.WaitGroup
	var money = 10000

	// 开启10个协程,每个协程内部 循环1000次,每次循环值+10
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {						
			for j := 0; j < 100; j++ {
				money += 10				//  多个协程对 money产生了竞争
			}
			wg.Done()
		}()
	}

	wg.Wait()


	fmt.Println("最终的monet = ", money)		// 应该输出20000才正确,但是每次运行输出结果不正确

}

等待组与互斥锁(同步锁)配合解决钱数问题示例:

package main

import (
	"fmt"
	"sync"
)

func main() {

	var mt sync.Mutex
	var wg sync.WaitGroup
	var money = 10000

	// 开启10个协程,每个协程内部 循环1000次,每次循环值+10
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(index int) {	
			mt.Lock()		
			fmt.Printf("协程 %d 抢到锁\n", index)			
			for j := 0; j < 100; j++ {
				money += 10				//  多个协程对 money产生了竞争
			}
			fmt.Printf("协程 %d 准备解锁\n", index)		
			mt.Unlock()
			wg.Done()
		}(i)
	}

	wg.Wait()


	fmt.Println("最终的monet = ", money)		// 应该输出20000才正确

}

可能的打印结果:

协程 9 抢到锁
协程 9 准备解锁
协程 4 抢到锁
协程 4 准备解锁
协程 0 抢到锁
协程 0 准备解锁
协程 1 抢到锁
协程 1 准备解锁
协程 2 抢到锁
协程 2 准备解锁
协程 3 抢到锁
协程 3 准备解锁
协程 7 抢到锁
协程 7 准备解锁
协程 5 抢到锁
协程 5 准备解锁
协程 8 抢到锁
协程 8 准备解锁
协程 6 抢到锁
协程 6 准备解锁
最终的monet =  20000

posted @ 2020-06-13 16:34  cool2feel  阅读(101)  评论(0)    收藏  举报