GO语言并发编程入门:Goroutine连接池/并发求数组和
ants连接池概述
参考文档:https://github.com/panjf2000/ants/blob/master/README_ZH.md
虽然Go的Goroutine非常强大,几乎可以随意创建,但是毕竟资源是有限的。当我们大批量重复使用Goroutine的时候,将会占用非常多的系统资源,这个时候Goroutine连接池就非常有用了。
ants是一个非常流行的Goroutine连接池工具,具备以下功能:
- 自动调度海量的 goroutines,复用 goroutines
- 定期清理过期的 goroutines,进一步节省资源
- 提供了大量有用的接口:任务提交、获取运行中的 goroutine 数量、动态调整 Pool 大小、释放 Pool、重启 Pool
- 优雅处理 panic,防止程序崩溃
- 资源复用,极大节省内存使用量;在大规模批量并发任务场景下比原生 goroutine 并发具有更高的性能
- 非阻塞机制
使用默认连接池
如何使用默认连接池提交任务
// 任务子函数
func demoFunc() {
time.Sleep(10 * time.Millisecond)
fmt.Println("Hello World!")
}
// 任务函数
var wg sync.WaitGroup
syncCalculateSum := func() {
demoFunc()
wg.Done()
}
// 提交任务
ants.Submit(syncCalculateSum)
并发执行1000次无参方法
package main
import (
"fmt"
"sync"
"time"
"github.com/panjf2000/ants/v2"
)
// 核心的方法
func demoFunc() {
time.Sleep(10 * time.Millisecond)
fmt.Println("Hello World!")
}
func main() {
defer ants.Release()
runTimes := 1000
// 使用通用的连接池
var wg sync.WaitGroup
syncCalculateSum := func() {
demoFunc()
wg.Done()
}
// 提交指定次数的任务
for i := 0; i < runTimes; i++ {
wg.Add(1)
// 使用默认的连接池
_ = ants.Submit(syncCalculateSum)
}
wg.Wait()
fmt.Printf("运行中的Goroutine数量: %d\n", ants.Running())
fmt.Printf("任务完成.\n")
}
使用自定义连接池
package main
import (
"fmt"
"sync"
"sync/atomic"
"github.com/panjf2000/ants/v2"
)
// 全局变量
var sum int32
// 核心的方法
func myFunc(i interface{}) {
n := i.(int32)
atomic.AddInt32(&sum, n)
fmt.Printf("run with %d\n", n)
}
func main() {
runTimes := 1000
// 使用通用的连接池
var wg sync.WaitGroup
// 创建连接池
p, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
myFunc(i)
wg.Done()
})
defer p.Release()
// 提交任务
for i := 0; i < runTimes; i++ {
wg.Add(1)
_ = p.Invoke(int32(i))
}
wg.Wait()
fmt.Printf("运行中的Goroutine数量: %d\n", ants.Running())
fmt.Println("任务完成:", sum)
}
使用连接池并发求数组和
package main
import (
"fmt"
"math"
"math/rand"
"sync"
"sync/atomic"
"github.com/panjf2000/ants/v2"
)
// 全局变量
var sum int64
// 核心的方法
func sumArr(i interface{}) {
arr := i.([]int64)
for _, v := range arr {
atomic.AddInt64(&sum, v)
}
}
// 获取随机数组
func getArr(min, max, length int) (arr []int64) {
for i := 0; i < length; i++ {
num := rand.Float64()*float64(max) + float64(min)
numInt := int64(math.Floor(num))
arr = append(arr, numInt)
}
return
}
func main() {
arr := getArr(0, 100, 100000000)
// 使用通用的连接池
var wg sync.WaitGroup
// 创建连接池
p, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
sumArr(i)
wg.Done()
})
defer p.Release()
// 拆分数组
step := len(arr) / 3
arr1 := arr[:step]
arr2 := arr[step : step+step]
arr3 := arr[step+step:]
// 提交任务
wg.Add(1)
_ = p.Invoke(arr1)
wg.Add(1)
_ = p.Invoke(arr2)
wg.Add(1)
_ = p.Invoke(arr3)
wg.Wait()
fmt.Printf("运行中的Goroutine数量: %d\n", ants.Running())
fmt.Println("任务完成:", sum)
}
比较连接池和普通方法求和消耗时间
package main
import (
"fmt"
"math"
"math/rand"
"sync"
"sync/atomic"
"time"
"github.com/panjf2000/ants/v2"
)
// 全局变量
var sum int64
// 核心的方法
func sumArr(i interface{}) {
arr := i.([]int64)
for _, v := range arr {
atomic.AddInt64(&sum, v)
}
}
// 获取随机数组
func getArr(min, max, length int) (arr []int64) {
for i := 0; i < length; i++ {
num := rand.Float64()*float64(max) + float64(min)
numInt := int64(math.Floor(num))
arr = append(arr, numInt)
}
return
}
// 求数组和
func getArrSum(arr []int64) (sum int64) {
for _, num := range arr {
sum += num
}
return
}
func main() {
var (
arr = getArr(0, 100, 100000000)
startTime time.Time
spendTime time.Duration
wg sync.WaitGroup
)
// 创建连接池
p, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
sumArr(i)
wg.Done()
})
defer p.Release()
// 拆分数组
startTime = time.Now()
step := len(arr) / 3
arr1 := arr[:step]
arr2 := arr[step : step+step]
arr3 := arr[step+step:]
// 提交任务
wg.Add(1)
_ = p.Invoke(arr1)
wg.Add(1)
_ = p.Invoke(arr2)
wg.Add(1)
_ = p.Invoke(arr3)
wg.Wait()
spendTime = time.Since(startTime)
fmt.Println("使用连接池求和:", sum, spendTime)
// 普通方法求和
startTime = time.Now()
sum = getArrSum(arr)
spendTime = time.Since(startTime)
fmt.Println("使用普通方法求和:", sum, spendTime)
}
输出结果:
使用连接池求和: 4950340694 1.323886243s 使用普通方法求和: 4950340694 46.091214ms
从结果可以发现,连接池消耗的时间反而比普通方法更多。主要是因为,连接池不仅要拆分数组,每次求和的时候,还需要单独将每个元素累加到全局变量sum上,会有额外的计算步骤。
即就是说,连接池有自己适用的使用场景,并非只要上连接池效率就高于一切,在真实的开发中,要根据实际需求考虑是否需要使用Goroutine连接池。
THE END






