1.要并发执行的大量任务,假如300个
package main
import "fmt"
func task() {
fmt.Println("task...")
}
func main() {
for i := 0; i < 300; i++ {
go task()
}
println("main over...")
}
执行如上代码,会出现task协程全部未运行结束main协程就运行结束了。
但是往往我们需要task协程全部运行结束才让main协程运行结束,那么我们就要再改善下上述代码
2.让大量并发任务运行结束后才main协程才运行结束
package main
import (
"fmt"
"sync"
)
func task(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("task...")
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 300; i++ {
wg.Add(1)
go task(&wg)
}
wg.Wait()
println("main over...")
}
运行结果如下:
3.最大并发数限制:
多次运行仍然时main over在最后打印,代表了main协程最后执行完毕。虽然300个协程都去执行了,但是大量协程并发执行的时候,有时候效率并不高,例如每个任务运行的时候调用了别的服务的API,太大量的并发调用会导致别的服务崩溃或者响应越来越慢等问题,因此最大并发数的限制也是必要的,例如我们限制最多50个task在执行:
package main
import (
"fmt"
"sync"
"time"
)
func task(wg *sync.WaitGroup, sema chan int) {
defer wg.Done()
defer func() {
<-sema
}()
time.Sleep(time.Second * 2)
fmt.Println("task...")
}
func main() {
var wg sync.WaitGroup
startTime := time.Now().Second()
sema := make(chan int, 50)
for i := 0; i < 300; i++ {
wg.Add(1)
sema <- i
go task(&wg, sema)
}
wg.Wait()
fmt.Printf("task token time %d s \n", time.Now().Second()-startTime)
println("main over...")
}
运行结果如下:
单个任务是耗时2秒钟(因为做了2秒的睡眠),如果300个一起执行,300个全部运行结束耗时是2秒,但是限制最大并发数为50后,耗时是12秒。
4.相同类型结果的获取
现在限制了最大并发数后还有一个问题,如果我要获取每个协程的返回数据,且数据都是同一类型的,那么我们可以把每个协程的结果放入同一个缓冲通道中:
package main
import (
"fmt"
"sync"
"time"
)
func task(wg *sync.WaitGroup, sema chan int, param int, taskResult chan int) {
defer wg.Done()
defer func() {
<-sema
}()
time.Sleep(time.Second * 2)
fmt.Println("task...")
taskResult <- param
}
func main() {
var wg sync.WaitGroup
startTime := time.Now().Second()
sema := make(chan int, 50)
taskResult := make(chan int, 300) //长度为结果总数
for i := 0; i < 300; i++ {
wg.Add(1)
sema <- i
go task(&wg, sema, i, taskResult)
}
wg.Wait()
fmt.Printf("task token time %d s \n", time.Now().Second()-startTime)
close(taskResult)
for v := range taskResult {
fmt.Printf("结果 v= %d\n", v)
}
println("main over...")
运行结果如下:
注意:存储结果的缓冲通道 用for range的方式遍历获取的时候记得要关闭通道,否则会报错。
原因是通道未关闭的时候,如果已经没有元素在通道中的话,仍然从通道中获取,那么main协程会一直等待别的协程给通道加入元素,形成了死锁。当然也可以用一种for index的方式获取,那么就不用关闭通道,如下:
运行结果不会报错:
但是用for index的方式遍历结果时要注意,跳出条件的判断不能直接使用i<len(chin),因为每次从通道中取出一个元素之后,len(chan)的结果就减少一个,因此我们只能取到通道的一半数据。