一、前言
go语言类似Java JUC包也提供了一些列用于多线程之间进行同步的措施,比如低级的同步措施有 锁、CAS、原子变量操作类。相比Java来说go提供了独特的基于通道的同步措施。本节我们先来看看go中WaitGroup
二、WaitGroup
在日常开发中经常会遇到这样的情况,就是一个线程需要等其他几个线程执行完毕后在执行一件事,比如常见的是在一个线程中切分一个大任务为几个小任务,然后多个子任务并发运行,切分线程则需要等子任务全部运行完毕后做汇总。
在go中sync包中的WaitGroup就可以来做多goroutine之间的同步,WaitGroup类似Java JUC包中的CyclicBarrier 、CountDownLatch、Semaphore。
在go中使用wg sync.WaitGroup就可以创建一个WaitGroup组,其方法 func(wg*WaitGroup)Add(deltaint)
作用可以认为是设置信号量个数, (wg*WaitGroup)Done()
可以认为是让信号量个数减去1, (wg*WaitGroup)Wait()
方法会一直阻塞,直到信号量变为了0。
需要注意调用Add方法时候如果delta传递为负数会抛出异常 panic:sync:negativeWaitGroupcounte
r。另外调用Add设置信号量的个数必须先于有goroutine调用其Wait方法。
下面我们看一个例子:
package main
import (
"fmt"
"sync"
)
var (
wg sync.WaitGroup //信号量
task []int//任务切片
)
func main() {
//1. threadNum个信号量
wg.Add(2)
task := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
//2.开启2个goroutine
go subTask(task, 1, 5)
go subTask(task, 6, 10)
//3.等待子任务执行完毕
wg.Wait()
fmt.Println("task over")
}
func subTask(task []int, start int, end int) {
defer wg.Done()
for i := start - 1; i <= end-1; i++ {
fmt.Println(task[i])
}
}
如上代码首先创建了2个信号量和一个切片
然后开启了两个goroutine分别执行subTask函数,subTask函数第一个参数是任务切片,后两个参数是要该方法访问切片中那些元素的索引范围
subTask内打印完对应的元素后,让信号量减去1,代码3则等待两个goroutine执行完毕后返回。
另外如果需要复用WaitGroup需要确保调用Add方法要晚于原来的Wait方法返回:
package main
import (
"fmt"
"sync"
)
var (
wg sync.WaitGroup //信号量
task []int //任务切片
)
func main() {
//1. threadNum个信号量
wg.Add(2)
task := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
//2.开启2个goroutine
go subTask(task, 1, 5)
go subTask(task, 6, 10)
//3.等待子任务执行完毕
wg.Wait()
fmt.Println("-----")
//4.复用wg,创建三个信号量
wg.Add(3)
go subTask(task, 1, 3)
go subTask(task, 4, 6)
go subTask(task, 7, 10)
//5.等待3个任务执行完毕
wg.Wait()
fmt.Println("task over")
}
func subTask(task []int, start int, end int) {
defer wg.Done()
for i := start - 1; i <= end-1; i++ {
fmt.Println(task[i])
}
}
如上代码4复用了wg,这个发生在代码3返回后。