简单线程池
和 Python 干货合集里面差不多,一个十分简短的线程池应用代码
主要负责:根据函数参数列表,无序批量地完成一系列任务,阻塞等待完成,返回结果列表,提供同时运行的最大线程数控制
package task
import (
"errors"
"fmt"
"math"
"sync"
)
type AnyCaller func(...any) any
// 同步列表
type SyncList struct {
items []any // 列表项
mutex sync.Mutex // 互斥锁
}
// 每个独立的任务报告类型
type Report struct {
result any
err any
}
func (r *Report) String() string {
return fmt.Sprintf("result: %v, err: %v", r.result, r.err)
}
func (l *SyncList) Add(items ...any) {
l.mutex.Lock()
l.items = append(l.items, items...)
defer l.mutex.Unlock()
}
func copyList(l []any) []any {
listCopy := make([]any, len(l))
copy(listCopy, l)
return listCopy
}
// 批量随机地完成所有的工作
func execute(f AnyCaller, args [][]any) []any {
var wg sync.WaitGroup
var resultList *SyncList = &SyncList{items: make([]any, 0)}
var l int = len(args)
for i := 0; i < l; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
defer func() {
// 处理未知异常
if r := recover(); r != nil {
resultList.Add(&Report{nil, r})
}
}()
returnResult := f(args[idx]...)
resultList.Add(&Report{returnResult, nil})
}(i)
}
wg.Wait()
return copyList(resultList.items)
}
// 使用线程池无序地完成一批工作, 返回结果列表
func MultiTask(f AnyCaller, args [][]any, maxTokents int) []any {
if maxTokents <= 0 {
panic(errors.New("不能创建空线程池"))
}
least := len(args)
var resultList *SyncList = &SyncList{items: make([]any, least)}
var taksNum int = int(math.Ceil(float64(least) / float64(maxTokents)))
for k := 1; k <= taksNum; k++ {
var right int = k * maxTokents
right = min(least, right)
res := execute(f, args[((k-1)*maxTokents):right])
resultList.Add(res...)
}
return copyList(resultList.items)
}
配套的测试代码 multi_test.go
package task
import (
"fmt"
"math/rand"
"testing"
"time"
)
func show(a ...int) {
fmt.Print(a)
}
func slice2str(s []any) string {
a := fmt.Sprintf("%v", s)
return a
}
func errorFunc(args []any) {
// panic("错误函数:" + slice2str(args))
a := []int{1, 2}
fmt.Println(a[3])
}
func normalFunc(args []any) string {
return fmt.Sprintf("正常执行的函数: %v", args)
}
func RandFunc(args ...any) any {
sec := time.Duration(5)
time.Sleep(sec * time.Second)
if rand.Intn(10) >= 7 {
errorFunc(args)
return -1
}
return normalFunc(args)
}
func TestMultiTask(t *testing.T) {
rand.New(rand.NewSource(312))
args := [][]any{
[]any{1, 2, 3},
[]any{4, 5, 6},
[]any{7, 8, 9},
[]any{10, 11, 12},
[]any{13, 14, 15},
[]any{16, 17, 18},
[]any{19, 20, 21},
[]any{22, 23, 24},
[]any{25, 26, 27},
}
for _, v := range MultiTask(RandFunc, args, 6) {
if v != nil {
t.Log(v)
}
}
}
小笔记
创建固定容量的 slice, 千万别把容量写到第二个参数,这样 slice 会预先填充多个 nil 值在前面的位置
make([]any, 0, x)
// 错误写法
make([]any, 3) // 结果 {nil, nil, nil}
copy
反而认的是 len
的值,就是说创建的 slice 要有预先的 len
值, copy
才能把元素放进去
复制一份 slice
func copyList(l []any) []any {
listCopy := make([]any, len(l))
copy(listCopy, l)
return listCopy
}