├── .gitignore ├── Makefile ├── .DS_Store ├── runner.go ├── internal ├── .DS_Store └── static │ ├── .DS_Store │ ├── GoHiveIcon.png │ └── class-diagram.drawio ├── go.mod ├── .travis.yml ├── go.sum ├── LICENSE ├── pool.go ├── pool_test.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | @go build 3 | 4 | test: 5 | @go test ./... -count=1 -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveleshsharma/gohive/HEAD/.DS_Store -------------------------------------------------------------------------------- /runner.go: -------------------------------------------------------------------------------- 1 | package gohive 2 | 3 | type Runner interface { 4 | Run() 5 | } 6 | -------------------------------------------------------------------------------- /internal/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveleshsharma/gohive/HEAD/internal/.DS_Store -------------------------------------------------------------------------------- /internal/static/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveleshsharma/gohive/HEAD/internal/static/.DS_Store -------------------------------------------------------------------------------- /internal/static/GoHiveIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveleshsharma/gohive/HEAD/internal/static/GoHiveIcon.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/loveleshsharma/gohive 2 | 3 | go 1.21.3 4 | 5 | require ( 6 | github.com/pkg/errors v0.9.1 7 | github.com/stretchr/testify v1.8.4 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.9.x" 5 | - "1.10.x" 6 | - "1.11.x" 7 | - "1.12.x" 8 | 9 | script: 10 | - go build 11 | - go test -coverprofile=coverage.txt -covermode=atomic -v 12 | 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) 15 | 16 | notifications: 17 | email: 18 | recipients: 19 | - lovelesh90@gmail.com -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 4 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 8 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Lovelesh Sharma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package gohive 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync/atomic" 7 | ) 8 | 9 | const ( 10 | OPEN = iota 11 | CLOSED 12 | ) 13 | 14 | type Pool struct { 15 | poolChan chan Runner 16 | quitChan chan bool 17 | state int32 18 | size int 19 | availableWorkers int32 20 | } 21 | 22 | func NewFixedPool(size int) *Pool { 23 | pool := &Pool{ 24 | poolChan: make(chan Runner), 25 | quitChan: make(chan bool), 26 | state: OPEN, 27 | size: size, 28 | } 29 | 30 | for i := 0; i < size; i++ { 31 | go pool.worker() 32 | pool.availableWorkers++ 33 | } 34 | 35 | return pool 36 | } 37 | 38 | func (p *Pool) Close() error { 39 | if atomic.CompareAndSwapInt32(&p.state, OPEN, CLOSED) { 40 | for i := 0; i < p.size; i++ { 41 | p.quitChan <- true 42 | } 43 | fmt.Println("pool is closed.") 44 | return nil 45 | } 46 | return errors.New("error: cannot close an already closed pool") 47 | } 48 | 49 | func (p *Pool) IsPoolClosed() bool { 50 | return atomic.LoadInt32(&p.state) == CLOSED 51 | } 52 | 53 | func (p *Pool) Submit(r Runner) error { 54 | if r == nil { 55 | return errors.New("cannot submit nil Runner") 56 | } 57 | 58 | if atomic.LoadInt32(&p.state) == CLOSED { 59 | return errors.New("cannot submit, pool is closed") 60 | } 61 | 62 | p.poolChan <- r 63 | return nil 64 | } 65 | 66 | func (p *Pool) worker() { 67 | defer fmt.Println("closing...") 68 | Loop: 69 | for { 70 | select { 71 | case r := <-p.poolChan: 72 | atomic.AddInt32(&p.availableWorkers, -1) 73 | r.Run() 74 | atomic.AddInt32(&p.availableWorkers, 1) 75 | case <-p.quitChan: 76 | break Loop 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pool_test.go: -------------------------------------------------------------------------------- 1 | package gohive 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | type testRunnable struct{} 10 | 11 | func (t testRunnable) Run() { 12 | for { 13 | time.Sleep(time.Millisecond * 100) 14 | } 15 | } 16 | 17 | var runnableObject = testRunnable{} 18 | 19 | func TestPool_CloseShouldReturnErrorIfAlreadyClosed(t *testing.T) { 20 | testPool := NewFixedPool(5) 21 | 22 | _ = testPool.Close() 23 | 24 | actualError := testPool.Close() 25 | 26 | assert.NotNil(t, actualError, "error should be not nil") 27 | 28 | } 29 | 30 | func TestPool_CloseShouldReturnNilIfPoolIsOpen(t *testing.T) { 31 | testPool := NewFixedPool(5) 32 | 33 | actualError := testPool.Close() 34 | 35 | assert.Nil(t, actualError, "error should be nil") 36 | } 37 | 38 | func TestPool_IsPoolClosedShouldReturnTrueOrFalseIfThePoolIsClosed(t *testing.T) { 39 | testPool := NewFixedPool(5) 40 | 41 | closed := testPool.IsPoolClosed() 42 | assert.Falsef(t, closed, "pool should not be closed") 43 | 44 | _ = testPool.Close() 45 | closed = testPool.IsPoolClosed() 46 | assert.True(t, closed, "pool should be closed") 47 | } 48 | 49 | func TestPool_SubmitShouldReturnErrorIfRunnableIsPassedAsNil(t *testing.T) { 50 | testPool := NewFixedPool(5) 51 | 52 | actualError := testPool.Submit(nil) 53 | 54 | assert.NotNil(t, actualError, "Submit should return error if runnable is nil") 55 | } 56 | 57 | func TestPool_SubmitShouldReturnErrorIfPoolIsClosed(t *testing.T) { 58 | testPool := NewFixedPool(5) 59 | 60 | _ = testPool.Close() 61 | actualError := testPool.Submit(runnableObject) 62 | 63 | assert.NotNil(t, actualError, "Submit should return error if runnable is nil") 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Package gohive implements a simple and easy to use goroutine pool for Go
5 |
11 | 12 | 13 | ## Features 14 | 15 | - Pool can be created with a specific size as per the requirement 16 | - Accepts tasks which implements Runner interface 17 | - Uses channels to accepts tasks and gets them executed via workers 18 | - Uses synchronization among workers to avoid race conditions 19 | 20 | ## Installation 21 | Use ```go get``` to install and update: 22 | ```go 23 | $ go get -u github.com/loveleshsharma/gohive 24 | ``` 25 | 26 | ## Usage 27 | 28 | - Create an instance of Pool type first 29 | 30 | ```go 31 | hive := gohive.NewFixedPool(5) 32 | ``` 33 | 34 | - Invoke the Submit() function and pass the task to execute 35 | 36 | ```go 37 | hive.Submit(object Runner) 38 | ``` 39 | Submit function accepts a Runner object as an argument, which it passes to the pool if a worker is available, otherwise it will wait for the worker to be available 40 | 41 | - To close the pool we can invoke the Close() function 42 | 43 | ```go 44 | hive.Close() 45 | ``` 46 | Once the pool is closed, we cannot assign any task to it 47 | 48 | ## Example 49 | 50 | Let's get into a full program where we can see how to use the gohive package in order to execute many goroutines simultaneously 51 | 52 | ```go 53 | package main 54 | 55 | import ( 56 | "fmt" 57 | "github.com/loveleshsharma/gohive" 58 | "sync" 59 | ) 60 | 61 | func main() { 62 | var wg sync.WaitGroup 63 | pool := gohive.NewFixedPool(5) 64 | 65 | for i := 1; i <= 20; i++ { 66 | if err := pool.Submit(NewMyStruct(i, &wg)); err != nil { 67 | fmt.Println("error: ", err) 68 | break 69 | } 70 | } 71 | 72 | wg.Wait() 73 | } 74 | 75 | type MyStruct struct { 76 | num int 77 | wg *sync.WaitGroup 78 | } 79 | 80 | func NewMyStruct(num int, wg *sync.WaitGroup) MyStruct { 81 | myStruct := MyStruct{ 82 | num: num, 83 | wg: wg, 84 | } 85 | wg.Add(1) 86 | return myStruct 87 | } 88 | 89 | func (s MyStruct) Run() { 90 | defer s.wg.Done() 91 | val := s.num 92 | fact := s.num 93 | for i := s.num - 1; i > 0; i-- { 94 | fact *= i 95 | } 96 | 97 | fmt.Printf("Factorial of %d: %d\n", val, fact) 98 | } 99 | 100 | 101 | ``` 102 | Important : Always keep sync.WaitGroup in your struct and put ```defer wg.Done()``` as the first statement of your Run() function. It will wait for your task to complete. 103 | 104 | ### 105 | TODO 106 | 1. Maintain a waiting queue to stop blocking submit method when all goroutines are busy. 107 | 2. Submitting priority tasks which takes priority over other tasks. 108 | 3. Handling panics inside goroutines to prevent them from crashing. 109 | 4. Implement dynamic pool which will scale the number of goroutines as per requirement and scales down when they are idle. 110 | 5. Submitting multiple tasks together. -------------------------------------------------------------------------------- /internal/static/class-diagram.drawio: -------------------------------------------------------------------------------- 1 |