Pool of concurrent workers with the ability of increment / decrement / pause / resume workers on demand.
- Enqueue jobs on demand
- Multiple ways to wait / block
- Add / kill workers on demand
- Be notified once a worker is started up / killed
- Multiple ways to kill workers:
- Change the number of workers on demand
- Pause / resume all workers
Golang version >= 1.9
Execute:
go get github.com/enriquebris/goworkerpool
Visit goworkerpool at godoc.org
Visit the TODO page.
package main
import (
"fmt"
"log"
"time"
"github.com/enriquebris/goworkerpool"
)
func main() {
var (
maxOperationsInQueue uint = 50
)
pool, err := goworkerpool.NewPoolWithOptions(goworkerpool.PoolOptions{
TotalInitialWorkers: 10,
MaxWorkers: 20,
MaxOperationsInQueue: maxOperationsInQueue,
WaitUntilInitialWorkersAreUp: true,
LogVerbose: true,
})
if err != nil {
fmt.Println(err)
return
}
// add the worker handler function
pool.SetWorkerFunc(func(data interface{}) bool {
log.Printf("processing %v\n", data)
// add a 1 second delay (to makes it look as it were processing the job)
time.Sleep(time.Second)
log.Printf("processing finished for: %v\n", data)
// let the pool knows that the worker was able to complete the task
return true
})
// enqueue jobs in a separate goroutine
go func() {
for i := 0; i < int(maxOperationsInQueue); i++ {
pool.AddTask(i)
}
// kill all workers after the currently enqueued jobs get processed
pool.LateKillAllWorkers()
}()
// wait while at least one worker is alive
pool.Wait()
}
See code examples at examples folder.
pool.SetWorkerFunc(fn)
This is the way to set the function that will be invoked from a worker each time it pulls a job from the queue. Internally, the worker will pass the job as a parameter to this function.
The function signature (PoolFunc):
func handler(interface{}) bool {
}
The handler function should return true to let know that the job was successfully processed, or false in other case.
pool.SetWorkerFunc(func(data interface{}) bool {
// do the processing
// let the pool knows that the worker was able to complete the task
return true
})
StartWorkers spins up the workers. The amount of workers to be started up is defined at the Pool instantiation.
pool.StartWorkers()
This is an asynchronous operation, but there is a way to be be notified each time a new worker is started up: through a channel. See SetNewWorkerChan(chan).
StartWorkersAndWait spins up the workers and wait until all of them are 100% up. The amount of workers to be started up is defined at the Pool instantiation.
pool.StartWorkersAndWait()
Although this is an synchronous operation, there is a way to be be notified each time a new worker is started up: through a channel. See SetNewWorkerChan(chan). Keep in mind that the channel listener should be running on a different goroutine.
SetNewWorkerChan(chan) sets a channel to receive notifications every time a new worker is started up.
pool.SetNewWorkerChan(ch chan<- int)
This is optional, no channel is needed to start up new workers. Basically is just a way to give feedback for the worker's start up operation.
AddTask will enqueue a job into a FIFO queue (a channel).
pool.AddTask(data)
The parameter for the job's data accepts any kind of value (interface{}).
Workers (if alive) will be listening to and picking up jobs from this queue. If no workers are alive nor idle, the job will stay in the queue until any worker will be ready to pick it up and start processing it.
The queue in which this function enqueues the jobs has a limit (it was set up at pool initialization). It means that AddTask will wait for a free queue slot to enqueue a new job in case the queue is at full capacity.
AddTask will return an error if no new tasks could be enqueued at the execution time. No new tasks could be enqueued during a certain amount of time when WaitUntilNSuccesses meets the stop condition.
AddTaskCallback will enqueue a job plus a callback function into a FIFO queue (a channel).
pool.AddTaskCallback(data, callback)
The parameter for the job's data accepts any type (interface{}).
Workers (if alive) will be listening to and picking up jobs from this queue. If no workers are alive nor idle, the job will stay in the queue until any worker will be ready to pick it up and start processing it.
The worker who picks up this job + callback will process the job first and later will invoke the callback function, passing the job's data as a parameter.
The queue in which this function enqueues the jobs has a limit (it was set up at pool initialization). It means that AddTaskCallback will wait for a free queue slot to enqueue a new job in case the queue is at full capacity.
AddTaskCallback will return an error if no new tasks could be enqueued at the execution time. No new tasks could be enqueued during a certain amount of time when WaitUntilNSuccesses meets the stop condition.
AddCallback will enqueue a callback function into a FIFO queue (a channel).
pool.AddCallback(callback)
Workers (if alive) will be listening to and picking up jobs from this queue. If no workers are alive nor idle, the job will stay in the queue until any worker will be ready to pick it up and start processing it.
The worker who picks up this task will only invoke the callback function, passing nil as a parameter.
The queue in which this function enqueues the jobs has a limit (it was set up at pool initialization). It means that AddCallback will wait for a free queue slot to enqueue a new job in case the queue is at full capacity.
AddCallback will return an error if no new tasks could be enqueued at the execution time. No new tasks could be enqueued during a certain amount of time when WaitUntilNSuccesses meets the stop condition.
AddComplexTask will enqueue a job into a FIFO queue (a channel).
pool.AddComplexTask(data, category, callback)
This function extends the scope of AddTask adding category and callback.
The job will be grouped based on the given category (for stats purposes).
The callback function (if any) will be invoked just after the job gets processed.
Let's suppose you have the following struct:
type JobData struct {
Filename string
Path string
Size uint64
}
then you can enqueue it as a job (to be processed by a worker):
pool.AddTask(JobData{
Filename: "file.txt",
Path: "/tmp/myfiles/",
Size: 1500,
})
Keep in mind that the worker's handler function needs to cast the parameter as a JobData (that is on your side).
Wait blocks until at least one worker is alive.
pool.Wait()
WaitUntilNSuccesses blocks until n jobs get successfully processed.
pool.WaitUntilNSuccesses(n)
AddWorker adds a new worker to the pool.
pool.AddWorker()
This is an asynchronous operation, but there is a way to be be notified each time a new worker is started up: through a channel.
AddWorkers adds n new workers to the pool.
pool.AddWorkers(n)
This is an asynchronous operation, but there is a way to be be notified each time a new worker is started up: through a channel.
KillWorker kills a live worker once it is idle or after it finishes with its current job.
pool.KillWorker()
This is an asynchronous operation, but there is a way to be be notified each time a worker is killed: through a channel. See SetKilledWorkerChan(chan).
KillWorkers kills all live workers. For those currently processing jobs, it will wait until the work is done.
pool.KillWorkers(n)
This is an asynchronous operation, but there is a way to be be notified each time a worker is killed: through a channel. See SetKilledWorkerChan(chan).
KillAllWorkers kills all live workers once they are idle or after they finish processing their current jobs.
pool.KillAllWorkers()
This is an asynchronous operation, but there is a way to be be notified each time a worker is killed: through a channel. See SetKilledWorkerChan(chan).
KillAllWorkersAndWait triggers an action to kill all live workers and blocks until the action is done (meaning that all live workers are down).
pool.KillAllWorkersAndWait()
This is an asynchronous operation, but there is a way to be be notified each time a worker is killed: through a channel. See SetKilledWorkerChan(chan).
LateKillWorker kills a worker after currently enqueued jobs get processed. If the worker is processing a job, it will be killed after the job gets processed.
By "currently enqueued jobs" I mean: the jobs enqueued at the moment this function was invoked.
pool.LateKillWorker()
This is an asynchronous operation, but there is a way to be be notified each time a worker is killed: through a channel. See SetKilledWorkerChan(chan).
LateKillWorkers kills n workers after currently enqueued jobs get processed. If the workers are processing jobs, they will be killed after the jobs get processed.
By "currently enqueued jobs" I mean: the jobs enqueued at the moment this function was invoked.
pool.LateKillWorkers(n)
This is an asynchronous operation, but there is a way to be be notified each time a worker is killed: through a channel. See SetKilledWorkerChan(chan).
LateKillAllWorkers kills all workers after currently enqueued jobs get processed. For those workers currently processing jobs, they will be killed after the jobs get processed.
By "currently enqueued jobs" I mean: the jobs enqueued at the moment this function was invoked.
pool.LateKillAllWorkers()
This is an asynchronous operation, but there is a way to be be notified each time a worker is killed: through a channel. See SetKilledWorkerChan(chan).
SetKilledWorkerChan(chan) sets a channel to receive notifications every time a worker is killed.
pool.SetKilledWorkerChan(ch chan int)
This is 100% optional.
SetTotalWorkers adjusts the number of live workers.
pool.SetTotalWorkers(n)
In case it needs to kill some workers (in order to adjust the total based on the given parameter), it will wait until their current jobs get processed (if they are processing jobs).
This is an asynchronous operation, but there is a way to be be notified each time a new worker is started up: through a channel.
It returns an error in the following scenarios:
- The workers were not started yet by StartWorkers.
- There is a "in course" KillAllWorkers operation.
PauseAllWorkers immediately pauses all workers (after they finish processing their current jobs). No new jobs will be pulled from the queue until ResumeAllWorkers() be invoked.
pool.PauseAllWorkers()
ResumeAllWorkers() resumes all workers.
pool.ResumeAllWorkers()
- Minor readme.md update
- Adjusted goconcurrentcounter parameters
- Added go.mod && go.sum files
-
Initial workers automatically start running on pool initialization
- deprecated StartWorkers()
-
Each new added worker is being automatically started
-
SafeWaitUntilNSuccesses: it waits until n tasks were successfully processed, but if any extra task is already "in progress", this function will wait until it is done. An extra enqueued task could started processing just before the nth expected task was finished.
-
GetTotalWorkersInProgress: returns total workers in progress.
-
KillAllWorkers returns error
-
KillAllWorkersAndWait returns error
-
SetTotalWorkers It won't return error because of the workers were not yet started, workers are now started once they are created.
-
WaitUntilInitialWorkersAreUp: it waits until all initial workers are up and running.
-
StartWorkers is deprecated. It only returns nil.
-
StartWorkersAndWait is deprecated. It returns WaitUntilInitialWorkersAreUp()
- Examples: Replaced pool.StartWorkers() by pool.StartWorkersAndWait()
- Added a way to know that new workers were started (using an optional channel)
- Added a way to know if a worker was killed (using an optional channel)
- StartWorkersAndWait() to start workers (for first time) and wait until all of them are alive
- Enqueue jobs plus callback functions
- Enqueue callback functions without jobs' data
- Fixed bug that caused randomly worker initialization error
- SetTotalWorkers() returns error in case it is invoked before StartWorkers()
- Fixed bug that prevents to start/add new workers after a Wait() function finishes.
- LateKillAllWorkers() will kill all alive workers (not only the number of workers that were alive when the function was invoked)
- Repository name modified to "goworkerpool"
- Pause / Resume all workers:
- PauseAllWorkers()
- ResumeAllWorkers()
- Workers will listen to higher priority channels first
- Workers will listen to broad messages (kill all workers, ...) before get signals from any other channel:
- KillAllWorkers()
- KillAllWorkersAndWait()
- Added function to kill all workers (send a broad message to all workers) and wait until it happens:
- pool.KillAllWorkersAndWait()
- Added code examples
- Make Wait() listen to a channel (instead of use an endless for loop)
- Sync actions over workers. A FIFO queue was created for the following actions:
- Add new worker
- Kill worker(s)
- Late kill worker(s)
- Set total workers
- Added function to adjust number of live workers:
- pool.SetTotalWorkers(n)
- Added function to kill all live workers after current jobs get processed:
- pool.LateKillAllWorkers()
- readme.md
- godoc
- code comments
First stable BETA version.