├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── callable.go ├── examples └── app.go ├── go.mod ├── internal ├── set.go └── set_test.go ├── runnable.go ├── scheduled_pool.go ├── scheduled_pool_test.go ├── threadpool.go ├── threadpool_test.go └── worker.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | sudo: enabled 4 | 5 | os: linux 6 | 7 | go: 8 | - 1.9.x 9 | 10 | install: true 11 | 12 | script: 13 | - go build 14 | - go vet . 15 | - go test -coverprofile=coverage.txt -covermode=atomic 16 | 17 | after_success: 18 | - bash <(curl -s https://codecov.io/bash) 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang Threadpool implementation 2 | [![Build Status](https://travis-ci.org/shettyh/threadpool.svg?branch=master)](https://travis-ci.org/shettyh/threadpool) 3 | [![codecov](https://codecov.io/gh/shettyh/threadpool/branch/master/graph/badge.svg)](https://codecov.io/gh/shettyh/threadpool) 4 | [![GoDoc](https://godoc.org/github.com/shettyh/threadpool?status.svg)](https://godoc.org/github.com/shettyh/threadpool) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/shettyh/threadpool)](https://goreportcard.com/report/github.com/shettyh/threadpool) 6 | 7 | Scalable threadpool implementation using Go to handle the huge network trafic. 8 | 9 | ## Install 10 | 11 | `go get github.com/shettyh/threadpool` 12 | 13 | ## Usage 14 | 15 | ### Threadpool 16 | - Implement `Runnable` interface for tha task that needs to be executed. For example 17 | 18 | 19 | ``` 20 | type MyTask struct { } 21 | 22 | func (t *MyTask) Run(){ 23 | // Do your task here 24 | } 25 | 26 | ``` 27 | - Create instance of `ThreadPool` with number of workers required and the task queue size 28 | ``` 29 | pool := threadpool.NewThreadPool(200,1000000) 30 | ``` 31 | - Create Task and execute 32 | ``` 33 | task:=&MyTask{} 34 | err := pool.Execute(task) 35 | ``` 36 | - Using `Callable` task 37 | ``` 38 | type MyTaskCallable struct { } 39 | 40 | func (c *MyTaskCallable) Call() interface{} { 41 | //Do task 42 | return result 43 | } 44 | 45 | //Execute callable task 46 | task := &MyTaskCallable{} 47 | future, err := pool.ExecuteFuture(task) 48 | 49 | //Check if the task is done 50 | isDone := future.IsDone() // true/false 51 | 52 | //Get response , blocking call 53 | result := future.Get() 54 | 55 | ``` 56 | - Close the pool 57 | ``` 58 | pool.Close() 59 | ``` 60 | 61 | ### Scheduled threadpool 62 | 63 | - Create instance of `ScheduledThreadPool` with number of workers required 64 | ``` 65 | schedulerPool:= threadpool.NewScheduledThreadPool(10) 66 | ``` 67 | - Create Task and schedule 68 | ``` 69 | task:=&MyTask{} 70 | pool.ScheduleOnce(task, time.Second*20) // Time delay is in seconds only as of now 71 | ``` 72 | - Close the pool 73 | ``` 74 | pool.Close() 75 | ``` 76 | -------------------------------------------------------------------------------- /callable.go: -------------------------------------------------------------------------------- 1 | package threadpool 2 | 3 | // Callable the tasks which returns the output after exit should implement this interface 4 | type Callable interface { 5 | Call() interface{} 6 | } 7 | 8 | // Future is the handle returned after submitting a callable task to the thread threadpool 9 | type Future struct { 10 | response chan interface{} 11 | done bool 12 | } 13 | 14 | // callableTask is internally used to wrap the callable and future together 15 | // So that the worker can send the response back through channel provided in Future object 16 | type callableTask struct { 17 | Task Callable 18 | Handle *Future 19 | } 20 | 21 | // Get returns the response of the Callable task when done 22 | // Is is the blocking call it waits for the execution to complete 23 | func (f *Future) Get() interface{} { 24 | return <-f.response 25 | } 26 | 27 | // IsDone returns true if the execution is already done 28 | func (f *Future) IsDone() bool { 29 | return f.done 30 | } 31 | -------------------------------------------------------------------------------- /examples/app.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shettyh/threadpool" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | pool := threadpool.NewThreadPool(2000, 100000) 11 | time.Sleep(20 * time.Minute) 12 | task := &myTask{ID: 123} 13 | pool.Execute(task) 14 | } 15 | 16 | type myTask struct { 17 | ID int64 18 | } 19 | 20 | func (m *myTask) Run() { 21 | fmt.Println("Running my task ", m.ID) 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/shettyh/threadpool 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /internal/set.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "sync" 4 | 5 | // Set type 6 | // This implementation is faster than slices or arrays because it internally uses map to store the data 7 | // All the values stored as the keys in the map and value is dummy boolean, just a place holder. 8 | // It stores the Unique elements only 9 | type Set struct { 10 | _map *sync.Map 11 | } 12 | 13 | // NewSet creates and returns new set 14 | func NewSet() *Set { 15 | set := new(Set) 16 | set._map = new(sync.Map) 17 | return set 18 | } 19 | 20 | // Add adds the value to the Set 21 | func (s *Set) Add(value interface{}) { 22 | s._map.Store(value, true) 23 | } 24 | 25 | // Remove the value from the Set 26 | func (s *Set) Remove(value interface{}) { 27 | s._map.Delete(value) 28 | } 29 | 30 | //Contains checks if the value exists in the Set and returns true if exists 31 | func (s *Set) Contains(value interface{}) bool { 32 | _, ok := s._map.Load(value) 33 | return ok 34 | } 35 | 36 | // GetAll returns all the values as Array 37 | func (s *Set) GetAll() []interface{} { 38 | values := make([]interface{}, 0) 39 | s._map.Range(func(key interface{}, value interface{}) bool { 40 | values = append(values, key) 41 | return true 42 | }) 43 | return values 44 | } 45 | 46 | // GetAllAsString returns the values as Array of string 47 | func (s *Set) GetAllAsString() []string { 48 | values := make([]string, 0) 49 | s._map.Range(func(key interface{}, value interface{}) bool { 50 | values = append(values, key.(string)) 51 | return true 52 | }) 53 | return values 54 | } 55 | 56 | // GetAllWithCap returns the data in set with max data return limit 57 | func (s *Set) GetAllWithCap(cap int) []interface{} { 58 | values := make([]interface{}, 0) 59 | 60 | s._map.Range(func(key interface{}, value interface{}) bool { 61 | values = append(values, key) 62 | if cap--; cap > 0 { 63 | return true 64 | } 65 | return false 66 | }) 67 | return values 68 | } 69 | -------------------------------------------------------------------------------- /internal/set_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "testing" 4 | 5 | var ( 6 | set *Set 7 | ) 8 | 9 | func TestNewSet(t *testing.T) { 10 | set = NewSet() 11 | } 12 | 13 | func TestSet_Add(t *testing.T) { 14 | set.Add(20) 15 | if ok := set.Contains(20); !ok { 16 | t.Fail() 17 | } 18 | } 19 | 20 | func TestSet_Remove(t *testing.T) { 21 | set.Remove(20) 22 | if ok := set.Contains(20); ok { 23 | t.Fail() 24 | } 25 | } 26 | 27 | func TestSet_Contains(t *testing.T) { 28 | set.Add(40) 29 | if ok := set.Contains(40); !ok { 30 | t.Fail() 31 | } 32 | } 33 | 34 | func TestSet_GetAll(t *testing.T) { 35 | data := set.GetAll() 36 | if len(data) != 1 { 37 | t.Fail() 38 | } 39 | } 40 | 41 | func TestSet_GetAllWithCap(t *testing.T) { 42 | set.Add(50) 43 | data := set.GetAllWithCap(1) 44 | if len(data) != 1 { 45 | t.Fail() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /runnable.go: -------------------------------------------------------------------------------- 1 | package threadpool 2 | 3 | // Runnable is interface for the jobs that will be executed by the threadpool 4 | type Runnable interface { 5 | Run() 6 | } 7 | -------------------------------------------------------------------------------- /scheduled_pool.go: -------------------------------------------------------------------------------- 1 | package threadpool 2 | 3 | import ( 4 | "github.com/shettyh/threadpool/internal" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // ScheduledThreadPool Schedules the task with the given delay 10 | type ScheduledThreadPool struct { 11 | workers chan chan interface{} 12 | tasks *sync.Map 13 | noOfWorkers int 14 | counter uint64 15 | counterLock sync.Mutex 16 | closeHandle chan bool 17 | } 18 | 19 | // NewScheduledThreadPool creates new scheduler thread threadpool with given number of workers 20 | func NewScheduledThreadPool(noOfWorkers int) *ScheduledThreadPool { 21 | pool := &ScheduledThreadPool{} 22 | pool.noOfWorkers = noOfWorkers 23 | pool.workers = make(chan chan interface{}, noOfWorkers) 24 | pool.tasks = new(sync.Map) 25 | pool.closeHandle = make(chan bool) 26 | pool.createPool() 27 | return pool 28 | } 29 | 30 | // createPool creates the workers threadpool 31 | func (stf *ScheduledThreadPool) createPool() { 32 | for i := 0; i < stf.noOfWorkers; i++ { 33 | worker := NewWorker(stf.workers, stf.closeHandle) 34 | worker.Start() 35 | } 36 | 37 | go stf.dispatch() 38 | } 39 | 40 | // dispatch will check for the task to run for current time and invoke the task 41 | func (stf *ScheduledThreadPool) dispatch() { 42 | for { 43 | select { 44 | case <-stf.closeHandle: 45 | //Stop the scheduler 46 | return 47 | default: 48 | go stf.intervalRunner() // Runner to check the task to run for current time 49 | time.Sleep(time.Second * 1) // Check again after 1 sec 50 | } 51 | } 52 | } 53 | 54 | // intervalRunner checks the tasks map and runs the tasks that are applicable at this point of time 55 | func (stf *ScheduledThreadPool) intervalRunner() { 56 | // update the time count 57 | stf.updateCounter() 58 | 59 | // Get the task for the counter value 60 | currentTasksToRun, ok := stf.tasks.Load(stf.counter) 61 | 62 | // Found tasks 63 | if ok { 64 | // Convert to tasks set 65 | currentTasksSet := currentTasksToRun.(*internal.Set) 66 | 67 | // For each tasks , get a worker from the threadpool and run the task 68 | for _, val := range currentTasksSet.GetAll() { 69 | go func(job interface{}) { 70 | // get the worker from threadpool who is free 71 | worker := <-stf.workers 72 | // Submit the job to the worker 73 | worker <- job 74 | }(val) 75 | } 76 | } 77 | } 78 | 79 | // updateCounter thread safe update of counter 80 | func (stf *ScheduledThreadPool) updateCounter() { 81 | stf.counterLock.Lock() 82 | defer stf.counterLock.Unlock() 83 | stf.counter++ 84 | } 85 | 86 | // ScheduleOnce the task with given delay 87 | func (stf *ScheduledThreadPool) ScheduleOnce(task Runnable, delay time.Duration) { 88 | scheduleTime := stf.counter + uint64(delay.Seconds()) 89 | existingTasks, ok := stf.tasks.Load(scheduleTime) 90 | 91 | // Create new set if no tasks are already there 92 | if !ok { 93 | existingTasks = internal.NewSet() 94 | stf.tasks.Store(scheduleTime, existingTasks) 95 | } 96 | // Add task 97 | existingTasks.(*internal.Set).Add(task) 98 | } 99 | 100 | // Close will close the thread threadpool 101 | // TODO: check the existing task before closing 102 | func (stf *ScheduledThreadPool) Close() { 103 | close(stf.closeHandle) 104 | } 105 | -------------------------------------------------------------------------------- /scheduled_pool_test.go: -------------------------------------------------------------------------------- 1 | package threadpool 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | var ( 9 | schedulerpool *ScheduledThreadPool 10 | ) 11 | 12 | func TestNewScheduledThreadPool(t *testing.T) { 13 | schedulerpool = NewScheduledThreadPool(2) 14 | } 15 | 16 | func TestScheduledThreadPool_Schedule(t *testing.T) { 17 | task := &TestTask{TestData: &TestData{Val: "pristine"}} 18 | schedulerpool.ScheduleOnce(task, time.Second*20) 19 | 20 | time.Sleep(5 * time.Second) 21 | 22 | // It should not be changed until 20 secs 23 | if task.TestData.Val != "pristine" { 24 | t.Fail() 25 | } 26 | 27 | time.Sleep(20 * time.Second) 28 | 29 | // It should be changed after 20 secs 30 | if task.TestData.Val == "pristine" { 31 | t.Fail() 32 | } 33 | } 34 | 35 | func TestScheduledThreadPool_Close(t *testing.T) { 36 | schedulerpool.Close() 37 | } 38 | -------------------------------------------------------------------------------- /threadpool.go: -------------------------------------------------------------------------------- 1 | package threadpool 2 | 3 | import "fmt" 4 | 5 | var ( 6 | ErrQueueFull = fmt.Errorf("queue is full, not able add the task") 7 | ) 8 | 9 | //ThreadPool type for holding the workers and handle the job requests 10 | type ThreadPool struct { 11 | queueSize int64 12 | noOfWorkers int 13 | 14 | jobQueue chan interface{} 15 | workerPool chan chan interface{} 16 | closeHandle chan bool // Channel used to stop all the workers 17 | } 18 | 19 | // NewThreadPool creates thread threadpool 20 | func NewThreadPool(noOfWorkers int, queueSize int64) *ThreadPool { 21 | threadPool := &ThreadPool{queueSize: queueSize, noOfWorkers: noOfWorkers} 22 | threadPool.jobQueue = make(chan interface{}, queueSize) 23 | threadPool.workerPool = make(chan chan interface{}, noOfWorkers) 24 | threadPool.closeHandle = make(chan bool) 25 | threadPool.createPool() 26 | return threadPool 27 | } 28 | 29 | func (t *ThreadPool) submitTask( task interface{}) error { 30 | // Add the task to the job queue 31 | if len(t.jobQueue) == int(t.queueSize) { 32 | return ErrQueueFull 33 | } 34 | t.jobQueue <- task 35 | return nil 36 | } 37 | 38 | // Execute submits the job to available worker 39 | func (t *ThreadPool) Execute(task Runnable) error { 40 | return t.submitTask(task) 41 | } 42 | 43 | // ExecuteFuture will submit the task to the threadpool and return the response handle 44 | func (t *ThreadPool) ExecuteFuture(task Callable) (*Future, error) { 45 | // Create future and task 46 | handle := &Future{response: make(chan interface{})} 47 | futureTask := callableTask{Task: task, Handle: handle} 48 | err := t.submitTask(futureTask) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return futureTask.Handle, nil 53 | } 54 | 55 | // Close will close the threadpool 56 | // It sends the stop signal to all the worker that are running 57 | //TODO: need to check the existing /running task before closing the threadpool 58 | func (t *ThreadPool) Close() { 59 | close(t.closeHandle) // Stops all the routines 60 | close(t.workerPool) // Closes the Job threadpool 61 | close(t.jobQueue) // Closes the job Queue 62 | } 63 | 64 | // createPool creates the workers and start listening on the jobQueue 65 | func (t *ThreadPool) createPool() { 66 | for i := 0; i < t.noOfWorkers; i++ { 67 | worker := NewWorker(t.workerPool, t.closeHandle) 68 | worker.Start() 69 | } 70 | 71 | go t.dispatch() 72 | 73 | } 74 | 75 | // dispatch listens to the jobqueue and handles the jobs to the workers 76 | func (t *ThreadPool) dispatch() { 77 | for { 78 | select { 79 | 80 | case job := <-t.jobQueue: 81 | // Got job 82 | func(job interface{}) { 83 | //Find a worker for the job 84 | jobChannel := <-t.workerPool 85 | //Submit job to the worker 86 | jobChannel <- job 87 | }(job) 88 | 89 | case <-t.closeHandle: 90 | // Close thread threadpool 91 | return 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /threadpool_test.go: -------------------------------------------------------------------------------- 1 | package threadpool 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | const ( 10 | NumberOfWorkers = 20 11 | QueueSize = int64(1000) 12 | ) 13 | 14 | var ( 15 | threadpool *ThreadPool 16 | ) 17 | 18 | func TestNewThreadPool(t *testing.T) { 19 | threadpool = NewThreadPool(NumberOfWorkers, QueueSize) 20 | } 21 | 22 | func TestThreadPool_Execute(t *testing.T) { 23 | data := &TestData{Val: "pristine"} 24 | task := &TestTask{TestData: data} 25 | threadpool.Execute(task) 26 | 27 | time.Sleep(2 * time.Second) 28 | fmt.Println("") 29 | 30 | if data.Val != "changed" { 31 | t.Fail() 32 | } 33 | } 34 | 35 | func TestThreadPool_ExecuteFuture(t *testing.T) { 36 | task := &TestTaskFuture{} 37 | handle, _ := threadpool.ExecuteFuture(task) 38 | response := handle.Get() 39 | if !handle.IsDone() { 40 | t.Fail() 41 | } 42 | fmt.Println("Thread done ", response) 43 | } 44 | 45 | func TestThreadPool_Close(t *testing.T) { 46 | threadpool.Close() 47 | } 48 | 49 | 50 | func TestQueueFullError(t *testing.T) { 51 | threadpool := NewThreadPool(0, 1) 52 | 53 | data := &TestData{Val: "pristine"} 54 | task := &TestTask{TestData: data} 55 | 56 | err := threadpool.Execute(task) 57 | if err != nil { 58 | t.Fail() 59 | } 60 | 61 | err = threadpool.Execute(task) 62 | if err == nil || err != ErrQueueFull { 63 | t.Fail() 64 | } 65 | 66 | threadpool.Close() 67 | } 68 | 69 | func TestQueueFullError_Future(t *testing.T) { 70 | threadpool := NewThreadPool(0, 1) 71 | 72 | task := &TestLongTaskFuture{} 73 | 74 | _, err := threadpool.ExecuteFuture(task) 75 | if err != nil { 76 | t.Fail() 77 | } 78 | 79 | _, err = threadpool.ExecuteFuture(task) 80 | if err == nil || err != ErrQueueFull { 81 | t.Fail() 82 | } 83 | 84 | threadpool.Close() 85 | } 86 | 87 | type TestTask struct { 88 | TestData *TestData 89 | } 90 | 91 | type TestData struct { 92 | Val string 93 | } 94 | 95 | func (t *TestTask) Run() { 96 | fmt.Println("Running the task") 97 | t.TestData.Val = "changed" 98 | } 99 | 100 | type TestLongTask struct { } 101 | 102 | func (t TestLongTask) Run() { 103 | time.Sleep(5 * time.Second) 104 | } 105 | 106 | type TestTaskFuture struct{} 107 | 108 | func (t *TestTaskFuture) Call() interface{} { 109 | return "Done" 110 | } 111 | 112 | 113 | type TestLongTaskFuture struct{} 114 | 115 | func (t *TestLongTaskFuture) Call() interface{} { 116 | time.Sleep(5 * time.Second) 117 | return "Done" 118 | } 119 | -------------------------------------------------------------------------------- /worker.go: -------------------------------------------------------------------------------- 1 | package threadpool 2 | 3 | // Worker type holds the job channel and passed worker threadpool 4 | type Worker struct { 5 | jobChannel chan interface{} 6 | workerPool chan chan interface{} 7 | closeHandle chan bool 8 | } 9 | 10 | // NewWorker creates the new worker 11 | func NewWorker(workerPool chan chan interface{}, closeHandle chan bool) *Worker { 12 | return &Worker{workerPool: workerPool, jobChannel: make(chan interface{}), closeHandle: closeHandle} 13 | } 14 | 15 | // Start starts the worker by listening to the job channel 16 | func (w Worker) Start() { 17 | go func() { 18 | for { 19 | // Put the worker to the worker threadpool 20 | w.workerPool <- w.jobChannel 21 | 22 | select { 23 | // Wait for the job 24 | case job := <-w.jobChannel: 25 | // Got the job 26 | w.executeJob(job) 27 | case <-w.closeHandle: 28 | // Exit the go routine when the closeHandle channel is closed 29 | return 30 | } 31 | } 32 | }() 33 | } 34 | 35 | // executeJob executes the job based on the type 36 | func (w Worker) executeJob(job interface{}) { 37 | // Execute the job based on the task type 38 | switch task := job.(type) { 39 | case Runnable: 40 | task.Run() 41 | break 42 | case callableTask: 43 | response := task.Task.Call() 44 | task.Handle.done = true 45 | task.Handle.response <- response 46 | break 47 | } 48 | } 49 | --------------------------------------------------------------------------------