go工作池模板记录
Go语言中的工作池(Worker Pool)是一种常用的并发模式,它可以有效地管理和复用一组固定数量的goroutine(Go协程),以处理并发任务。
工作池的基本思想是预先创建一组goroutine,这些goroutine可以并行地执行任务。当有新的任务到达时,它们会被提交到工作池,由其中的一个空闲goroutine来处理任务。一旦任务完成,该goroutine就会回到空闲状态,可以继续处理下一个任务
package workPool
import (
"fmt"
"sync"
"testing"
)
type Job struct {
// 定义任务结构
// 可根据实际需求进行修改和扩展
ID int
Data interface{}
}
type Result struct {
// 定义结果结构
// 可根据实际需求进行修改和扩展
JobID int
Data interface{}
}
type Worker struct {
ID int
JobChannel <-chan Job
ResultChan chan<- Result
QuitChan chan bool
}
func NewWorker(id int, jobChannel <-chan Job, resultChan chan<- Result) *Worker {
return &Worker{
ID: id,
JobChannel: jobChannel,
ResultChan: resultChan,
QuitChan: make(chan bool),
}
}
func (w *Worker) Start() {
go func() {
for {
select {
case job := <-w.JobChannel:
result := w.Process(job)
w.ResultChan <- result
case <-w.QuitChan:
return
}
}
}()
}
func (w *Worker) Process(job Job) Result {
// 执行任务的逻辑
// 可根据实际需求进行修改和扩展
result := Result{
JobID: job.ID,
Data: nil, // 根据任务逻辑设置结果数据
}
return result
}
type Pool struct {
Workers []*Worker
JobChannel chan Job
ResultChan chan Result
QuitChannel chan bool
wg sync.WaitGroup
}
func NewPool(numWorkers, jobQueueSize, resultQueueSize int) *Pool {
workers := make([]*Worker, numWorkers)
jobChannel := make(chan Job, jobQueueSize)
resultChannel := make(chan Result, resultQueueSize)
quitChannel := make(chan bool)
return &Pool{
Workers: workers,
JobChannel: jobChannel,
ResultChan: resultChannel,
QuitChannel: quitChannel,
}
}
func (p *Pool) Start() {
for _, worker := range p.Workers {
worker.Start()
}
go p.processResults()
}
func (p *Pool) processResults() {
for result := range p.ResultChan {
// 处理结果的逻辑
// 可根据实际需求进行修改和扩展
fmt.Printf("Job ID: %d, Result: %v\n", result.JobID, result.Data)
p.wg.Done()
}
p.QuitChannel <- true
}
func (p *Pool) AddJob(job Job) {
p.wg.Add(1)
p.JobChannel <- job
}
func (p *Pool) Wait() {
p.wg.Wait()
close(p.ResultChan)
<-p.QuitChannel
}
func TestDemo(t *testing.T) {
numWorkers := 3
jobQueueSize := 10
resultQueueSize := 10
pool := NewPool(numWorkers, jobQueueSize, resultQueueSize)
for i := 0; i < numWorkers; i++ {
worker := NewWorker(i+1, pool.JobChannel, pool.ResultChan)
pool.Workers[i] = worker
}
pool.Start()
// 添加任务到工作池
for i := 0; i < jobQueueSize; i++ {
job := Job{
ID: i + 1,
Data: nil, // 根据任务逻辑设置任务数据
}
pool.AddJob(job)
}
pool.Wait()
fmt.Println("All jobs completed.")
}
在这个模板化的工作池示例中,您可以根据实际项目的需求进行修改和扩展。
Job
结构定义了任务的字段,您可以根据实际需求进行修改和扩展。Result
结构定义了结果的字段,您可以根据实际需求进行修改和扩展。Worker
结构表示工作池中的一个worker,其中的Process
方法定义了执行任务的逻辑,您可以根据实际需求进行修改。Pool
结构包含了一个worker集合、任务通道、结果通道和退出通道,其中的processResults
方法用于处理任务结果,您可以根据实际需求进行修改。NewPool
函数用于创建一个新的工作池,其中的参数可以根据实际项目需求进行调整。AddJob
方法用于将任务添加到工作池中。Wait
方法用于等待所有任务完成,并确保工作池中的所有goroutine都已退出。
通过修改和扩展上述代码,您可以根据实际项目的需求来创建一个适用的工作池。您可以调整worker的数量、定义适当的任务和结果结构,以及自定义任务处理逻辑和结果处理逻辑。
问题一:为什么正在执行任务的协程不会去竞争新的任务
回答:
在上述代码中,当有10个任务被提交到工作池,而工作协程只有3个时,情况如下:
前3个任务会立即被分配给3个工作协程,并开始执行任务。
一旦工作协程完成了一个任务,它会从JobChannel中获取下一个可用任务并继续执行。
后续任务会被放入JobChannel中等待被工作协程取出并执行。
在这个过程中,如果没有空闲的工作协程可用,即所有工作协程都在忙于执行任务,那么提交的任务会在JobChannel中等待,直到有工作协程空闲并从通道中接收任务为止。这就是为什么后续任务会被阻塞的原因。
默认情况下,case job := <-w.JobChannel是阻塞的。如果将default分支添加到select语句中,它可以执行一些其他操作,例如丢弃任务或打印日志,但并不会改变工作协程是否阻塞在case job := <-w.JobChannel这一行上。