【golang】WorkerPool 三种实现方式

11 篇文章 0 订阅

 1. 回收结果的 workerpool 常规实现

  工作线程和任务数相同

 

package wpool

import (
	"context"
)

type JobID string
type jobType string
type jobMetadata map[string]interface{}

// 执行函数
type ExecutionFn func(ctx context.Context, args interface{}) (interface{}, error)

// 任务描述,任务ID可以作为唯一标识
type JobDescriptor struct {
	ID       JobID
	JType    jobType
	Metadata map[string]interface{}
}

// 结果结构
type Result struct {
	Value      interface{}
	Err        error
	Descriptor JobDescriptor
}

// 可执行的任务
type Job struct {
	Descriptor JobDescriptor
	ExecFn     ExecutionFn
	Args       interface{}
}


// 执行方法
func (j Job) execute(ctx context.Context) Result {
	value, err := j.ExecFn(ctx, j.Args)
	if err != nil {
		return Result{
			Err:        err,
			Descriptor: j.Descriptor,
		}
	}

    // 携带任务描述信息
	return Result{
		Value:      value,
		Descriptor: j.Descriptor,
	}
}





package wpool

import (
	"context"
	"fmt"
	"sync"
)

// 工作者工作
func worker(ctx context.Context, wg *sync.WaitGroup, jobs <-chan Job, results chan<- Result) {
	defer wg.Done()
	for {
		select {
		case job, ok := <-jobs:
			if !ok {
				return
			}
		
            // 扇入多路复用 results channel ;fan-in job execution multiplexing results into the results channel
			results <- job.execute(ctx)
		case <-ctx.Done():
			fmt.Printf("cancelled worker. Error detail: %v\n", ctx.Err())
			results <- Result{
				Err: ctx.Err(),
			}
			return
		}
	}
}

type WorkerPool struct {
	workersCount int
	jobs         chan Job
	results      chan Result
	Done         chan struct{}
}

// 初始化一个workerpool
func NewWorkerPool(wcount int) WorkerPool {
	return WorkerPool{
		workersCount: wcount,
		jobs:         make(chan Job, wcount),
		results:      make(chan Result, wcount),
		Done:         make(chan struct{}),
	}
}

// 启动workerpool
func (wp WorkerPool) Run(ctx context.Context) {
	var wg sync.WaitGroup

	for i := 0; i < wp.workersCount; i++ {
		wg.Add(1)
		// fan out worker goroutines
		//reading from jobs channel and
		//pushing calcs into results channel
		go worker(ctx, &wg, wp.jobs, wp.results)
	}

	wg.Wait()
	close(wp.Done)
	close(wp.results)
}

// 获取结果
func (wp WorkerPool) Results() <-chan Result {
	return wp.results
}

// 任务输入
func (wp WorkerPool) GenerateFrom(jobsBulk []Job) {
	for i, _ := range jobsBulk {
		wp.jobs <- jobsBulk[i]
	}
	close(wp.jobs)
}

 回收结果的WorkerPool Test 单元测试实现

package wpool

import (
	"context"
	"fmt"
	"strconv"
	"testing"
	"time"
)

const (
	jobsCount   = 10
	workerCount = 2
)

// 完整的输入 --- > 读取result channel 结果
func TestWorkerPool(t *testing.T) {
	wp := New(workerCount)

	ctx, cancel := context.WithCancel(context.TODO())
	defer cancel()

	go wp.GenerateFrom(testJobs())

	go wp.Run(ctx)

	for {
		select {
		case r, ok := <-wp.Results():
			if !ok {
				continue
			}

			i, err := strconv.ParseInt(string(r.Descriptor.ID), 10, 64)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			val := r.Value.(int)
			if val != int(i)*2 {
				t.Fatalf("wrong value %v; expected %v", val, int(i)*2)
			}
		case <-wp.Done:
			return
		default:
		}
	}
}

// 测试超时
func TestWorkerPool_TimeOut(t *testing.T) {
	wp := New(workerCount)

	ctx, cancel := context.WithTimeout(context.TODO(), time.Nanosecond*10)
	defer cancel()

	go wp.Run(ctx)

	for {
		select {
		case r := <-wp.Results():
			if r.Err != nil && r.Err != context.DeadlineExceeded {
				t.Fatalf("expected error: %v; got: %v", context.DeadlineExceeded, r.Err)
			}
		case <-wp.Done:
			return
		default:
		}
	}
}

// 测试取消
func TestWorkerPool_Cancel(t *testing.T) {
	wp := New(workerCount)

	ctx, cancel := context.WithCancel(context.TODO())

	go wp.Run(ctx)
	cancel()

	for {
		select {
		case r := <-wp.Results():
			if r.Err != nil && r.Err != context.Canceled {
				t.Fatalf("expected error: %v; got: %v", context.Canceled, r.Err)
			}
		case <-wp.Done:
			return
		default:
		}
	}
}

// mock testJobs 输入
func testJobs() []Job {
	jobs := make([]Job, jobsCount)
	for i := 0; i < jobsCount; i++ {
		jobs[i] = Job{
			Descriptor: JobDescriptor{
				ID:       JobID(fmt.Sprintf("%v", i)),
				JType:    "anyType",
				Metadata: nil,
			},
			ExecFn: execFn,
			Args:   i,
		}
	}
	return jobs
}


package wpool

import (
	"context"
	"errors"
	"reflect"
	"testing"
)

var (
	errDefault = errors.New("wrong argument type")
    // 任务描述
	descriptor = JobDescriptor{
		ID:    JobID("1"),
		JType: jobType("anyType"),
		Metadata: jobMetadata{
			"foo": "foo",
			"bar": "bar",
		},
	}
    // 执行函数
	execFn = func(ctx context.Context, args interface{}) (interface{}, error) {
		argVal, ok := args.(int)
		if !ok {
			return nil, errDefault
		}

		return argVal * 2, nil
	}
)

func Test_job_Execute(t *testing.T) {
	ctx := context.TODO()

	type fields struct {
		descriptor JobDescriptor
		execFn     ExecutionFn
		args       interface{}
	}
	tests := []struct {
		name   string
		fields fields
		want   Result
	}{
		{
			name: "job execution success",
			fields: fields{
				descriptor: descriptor,
				execFn:     execFn,
				args:       10,
			},
			want: Result{
				Value:      20,
				Descriptor: descriptor,
			},
		},
		{
			name: "job execution failure",
			fields: fields{
				descriptor: descriptor,
				execFn:     execFn,
				args:       "10",
			},
			want: Result{
				Err:        errDefault,
				Descriptor: descriptor,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			j := Job{
				Descriptor: tt.fields.descriptor,
				ExecFn:     tt.fields.execFn,
				Args:       tt.fields.args,
			}

			got := j.execute(ctx)
			if tt.want.Err != nil {
				if !reflect.DeepEqual(got.Err, tt.want.Err) {
					t.Errorf("execute() = %v, wantError %v", got.Err, tt.want.Err)
				}
				return
			}

			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("execute() = %v, want %v", got, tt.want)
			}
		})
	}
}

2. 工作线程、任务数可配置无结果回收的WorkerPool

poolConfig IWorker IJob 接口定义

JboErr定义

/*
ErrNoAvaialableWorkers is used to describe a situation where there
are no workers available to work a job
*/
type ErrNoAvaialableWorkers struct {
	Job Job
}

func (e ErrNoAvaialableWorkers) Error() string {
	return "No workers available"
}

/*
GetJob returns the job associated with this error
*/
func (e ErrNoAvaialableWorkers) GetJob() Job {
	return e.Job
}

 IPool 接口 定义和实现

 IPool接口 Start QueueJob Wait接口实现

测试: 

package main

import (
	"fmt"
	"time"

	"pkg/workerpool"
)

type Job struct {
	Index int
}

func (j *Job) Work(workerID int) {
	fmt.Printf("Worker %d sleeping on index %d...\n", workerID, j.Index)
	time.Sleep(2 * time.Second)
}

func main() {
	var pool workerpool.IPool

	pool = workerpool.NewPool(workerpool.PoolConfig{
		MaxJobQueue:       100,
		MaxWorkers:        10,
		MaxWorkerWaitTime: 3 * time.Second,
	})

	pool.Start()

	for index := 0; index < 30; index++ {
		job := &Job{Index: index}
		pool.QueueJob(job)
	}

	pool.Wait()
	pool.Shutdown()
}

3. 根据任务特征分配到指定worker的 WorkerPool

type IJob interface {
    GetJobKey()   int64 // hashCode % workerNum decide which worker 
	Work(workerID int)  // workerID 执行该片段的workerID
}


type IPool interface {
	PutWorkerInTheQueue(worker IWorker)
	Shutdown()
	Start()
	Dispatch(job IJob)  // 分配job
	Wait()
}


type Pool struct {
	dispatchJobs    *sync.WaitGroup
	config          PoolConfig
	shutdownChannel chan bool
	workers         map[int32]IWorker

	ErrorQueue chan JobError
}


func NewPool(config PoolConfig) *Pool {
	return &Pool{
		dispatchJobs:    &sync.WaitGroup{},
		config:          config,
		shutdownChannel: make(chan bool),
		workers:         make(map[int32]IWorker, config.NumOfWorkers),

		ErrorQueue: make(chan JobError),
	}
}

type IWorker interface {
	DoJob(job Job)
	GetID() int
}


/*
 *每个Worker 都有一个*Job队列
*/
type Worker struct {
	Pool        IPool
	WaitGroup   *sync.WaitGroup
	WorkerID    int
    JobQueue    chan IJob
}


func (w *Worker) DoJob(job Job) {
	go func(j Job) {
		j.Work(w.WorkerID)
		w.WaitGroup.Done()
	}(job)
}

/*
GetID returns this worker's ID
*/
func (w *Worker) GetID() int {
	return w.WorkerID
}

/*
 根据任务特性分发任务,实现特定特征的任务分配指定worker
*/
func (p *Pool) Dispatch(job IJob) {
	p.dispatchJobs.Add(1)
    whi := job.GetJobKey()%len(p.workers)
    p.workers[whi].JobQueue <- job
}


func (p *Pool) Start() {
	for index := 0; index < p.config.NumOfWorkers; index++ {
		p.workers[index] <- &Worker{
			Pool:      p,
			WaitGroup: p.dispatchJobs,
			WorkerID:  index,
            JobQueue:  make(chan IJob, p.config.NumOfJobsEachWorker),
		}
	}

   for _,w := range p.workers {
	 go func() {
		 for {
		    	select {
			    case <-p.shutdownChannel:
			    	break

			    case job := <-w.JobQueue:
			    	worker.DoJob(job)
                    
			    }
		    }
	    }()
    }
}

func (p *Pool) Wait() {
	p.dispatchJobs.Wait()
}

type Job struct {
    Key   string
	Index int
}


func (j *Job) Work(workerID int) {
	fmt.Printf("Worker %d sleeping on index %d...\n", workerID, j.Index)
	time.Sleep(2 * time.Second)
}

func (j *Job) GetJobKey() int64{
	return md5.hashCode(j.Key)
}


func main() {
	var pool workerpool.IPool

	pool = workerpool.NewPool(workerpool.PoolConfig{
		NumOfJobEachWorker:       10,
		NumOfWorkers:             10,
	})

	pool.Start()

	for index := 0; index < 30; index++ {
		job := &Job{Index: index}
		pool.Dispatch(job)
	}

	pool.Wait()
	pool.Shutdown()
}



4. 小结

实现方式很多,适合自己的就是最好的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自驱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值