├── CondVar.cpp ├── CondVar.h ├── Global.h ├── Makefile ├── Mutex.cpp ├── Mutex.h ├── README.md ├── Task.cpp ├── Task.h ├── ThreadPool.cpp ├── ThreadPool.h ├── bin ├── example │ └── threadpool_test └── examplethreadpool_test └── threadpool_test.cpp /CondVar.cpp: -------------------------------------------------------------------------------- 1 | #include "CondVar.h" 2 | 3 | CondVar::CondVar() { 4 | pthread_cond_init(&m_cond_var, NULL); 5 | } 6 | CondVar::~CondVar() { 7 | pthread_cond_destroy(&m_cond_var); 8 | } 9 | void CondVar::wait(pthread_mutex_t* mutex) { 10 | pthread_cond_wait(&m_cond_var, mutex); 11 | } 12 | void CondVar::signal() { 13 | pthread_cond_signal(&m_cond_var); 14 | } 15 | void CondVar::broadcast() { 16 | pthread_cond_broadcast(&m_cond_var); 17 | } 18 | -------------------------------------------------------------------------------- /CondVar.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Global.h" 10 | 11 | using namespace std; 12 | 13 | class CondVar { 14 | public: 15 | 16 | CondVar(); 17 | ~CondVar(); 18 | void wait(pthread_mutex_t* mutex); 19 | void signal(); 20 | void broadcast(); 21 | 22 | private: 23 | pthread_cond_t m_cond_var; 24 | }; 25 | -------------------------------------------------------------------------------- /Global.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const int DEFAULT_POOL_SIZE = 10; 4 | const int STARTED = 0; 5 | const int STOPPED = 1; 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OBJPATH=bin/obj 2 | EXAMPLEPATH=bin/example 3 | 4 | all: 5 | g++ CondVar.cpp -lpthread -c -o $(OBJPATH)/CondVar.o 6 | g++ Mutex.cpp -lpthread -c -o $(OBJPATH)/Mutex.o 7 | g++ Task.cpp -lpthread -c -o $(OBJPATH)/Task.o 8 | g++ ThreadPool.cpp -lpthread -c -o $(OBJPATH)/ThreadPool.o 9 | g++ $(OBJPATH)/CondVar.o $(OBJPATH)/Mutex.o $(OBJPATH)/Task.o $(OBJPATH)/ThreadPool.o threadpool_test.cpp -lpthread -o $(EXAMPLEPATH)threadpool_test 10 | 11 | #all: 12 | # g++ threadpool.cpp -lpthread -fpic -c -o bin/obj/threadpool.o 13 | # g++ -L./bin bin/obj/threadpool.o -lpthread threadpool_test.cpp -o bin/example/threadpool_test 14 | 15 | #threadpool: 16 | # g++ threadpool.cpp -lpthread -fpic -c -o bin/obj/threadpool.o 17 | # g++ -shared -fPIC bin/obj/threadpool.o -o bin/lib/libthreadpool.so 18 | #example: 19 | # g++ -L./bin/lib -lthreadpool threadpool_test.cpp -o threadpool_test 20 | -------------------------------------------------------------------------------- /Mutex.cpp: -------------------------------------------------------------------------------- 1 | #include "Mutex.h" 2 | 3 | Mutex::Mutex() { 4 | pthread_mutex_init(&m_lock, NULL); 5 | is_locked = false; 6 | } 7 | 8 | Mutex::~Mutex() { 9 | while(is_locked); 10 | unlock(); // Unlock Mutex after shared resource is safe 11 | pthread_mutex_destroy(&m_lock); 12 | } 13 | 14 | void Mutex::lock() { 15 | pthread_mutex_lock(&m_lock); 16 | is_locked = true; 17 | } 18 | 19 | void Mutex::unlock() { 20 | is_locked = false; // do it BEFORE unlocking to avoid race condition 21 | pthread_mutex_unlock(&m_lock); 22 | } 23 | 24 | pthread_mutex_t* Mutex::get_mutex_ptr(){ 25 | return &m_lock; 26 | } 27 | -------------------------------------------------------------------------------- /Mutex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "Global.h" 12 | 13 | using namespace std; 14 | 15 | class Mutex 16 | { 17 | public: 18 | Mutex(); 19 | ~Mutex(); 20 | void lock(); 21 | void unlock(); 22 | pthread_mutex_t* get_mutex_ptr(); 23 | 24 | private: 25 | pthread_mutex_t m_lock; 26 | volatile bool is_locked; 27 | }; 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | threadpool 2 | ========== 3 | 4 | A very simple C++ threadpool based on POSIX threads (also known as pthreads). 5 | Sample usage is also provided in the test code. 6 | -------------------------------------------------------------------------------- /Task.cpp: -------------------------------------------------------------------------------- 1 | #include "Task.h" 2 | 3 | Task::Task(void (*fn_ptr)(void*), void* arg) : m_fn_ptr(fn_ptr), m_arg(arg) { 4 | } 5 | 6 | Task::~Task() { 7 | } 8 | 9 | void Task::operator()() { 10 | (*m_fn_ptr)(m_arg); 11 | if (m_arg != NULL) { 12 | delete m_arg; 13 | } 14 | } 15 | 16 | void Task::run() { 17 | (*m_fn_ptr)(m_arg); 18 | } 19 | -------------------------------------------------------------------------------- /Task.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "Global.h" 12 | 13 | using namespace std; 14 | 15 | //template 16 | class Task 17 | { 18 | public: 19 | // Task(TCLass::* obj_fn_ptr); // pass an object method pointer 20 | Task(void (*fn_ptr)(void*), void* arg); // pass a free function pointer 21 | ~Task(); 22 | void operator()(); 23 | void run(); 24 | private: 25 | // TClass* _obj_fn_ptr; 26 | void (*m_fn_ptr)(void*); 27 | void* m_arg; 28 | }; 29 | -------------------------------------------------------------------------------- /ThreadPool.cpp: -------------------------------------------------------------------------------- 1 | #include "ThreadPool.h" 2 | 3 | ThreadPool::ThreadPool() : m_pool_size(DEFAULT_POOL_SIZE) 4 | { 5 | cout << "Constructed ThreadPool of size " << m_pool_size << endl; 6 | } 7 | 8 | ThreadPool::ThreadPool(int pool_size) : m_pool_size(pool_size) 9 | { 10 | cout << "Constructed ThreadPool of size " << m_pool_size << endl; 11 | } 12 | 13 | ThreadPool::~ThreadPool() 14 | { 15 | // Release resources 16 | if (m_pool_state != STOPPED) { 17 | destroy_threadpool(); 18 | } 19 | } 20 | 21 | // We can't pass a member function to pthread_create. 22 | // So created the wrapper function that calls the member function 23 | // we want to run in the thread. 24 | extern "C" 25 | void* start_thread(void* arg) 26 | { 27 | ThreadPool* tp = (ThreadPool*) arg; 28 | tp->execute_thread(); 29 | return NULL; 30 | } 31 | 32 | int ThreadPool::initialize_threadpool() 33 | { 34 | // TODO: COnsider lazy loading threads instead of creating all at once 35 | m_pool_state = STARTED; 36 | int ret = -1; 37 | for (int i = 0; i < m_pool_size; i++) { 38 | pthread_t tid; 39 | ret = pthread_create(&tid, NULL, start_thread, (void*) this); 40 | if (ret != 0) { 41 | cerr << "pthread_create() failed: " << ret << endl; 42 | return -1; 43 | } 44 | m_threads.push_back(tid); 45 | } 46 | cout << m_pool_size << " threads created by the thread pool" << endl; 47 | 48 | return 0; 49 | } 50 | 51 | int ThreadPool::destroy_threadpool() 52 | { 53 | // Note: this is not for synchronization, its for thread communication! 54 | // destroy_threadpool() will only be called from the main thread, yet 55 | // the modified m_pool_state may not show up to other threads until its 56 | // modified in a lock! 57 | m_task_mutex.lock(); 58 | m_pool_state = STOPPED; 59 | m_task_mutex.unlock(); 60 | cout << "Broadcasting STOP signal to all threads..." << endl; 61 | m_task_cond_var.broadcast(); // notify all threads we are shttung down 62 | 63 | int ret = -1; 64 | for (int i = 0; i < m_pool_size; i++) { 65 | void* result; 66 | ret = pthread_join(m_threads[i], &result); 67 | cout << "pthread_join() returned " << ret << ": " << strerror(errno) << endl; 68 | m_task_cond_var.broadcast(); // try waking up a bunch of threads that are still waiting 69 | } 70 | cout << m_pool_size << " threads exited from the thread pool" << endl; 71 | return 0; 72 | } 73 | 74 | void* ThreadPool::execute_thread() 75 | { 76 | Task* task = NULL; 77 | cout << "Starting thread " << pthread_self() << endl; 78 | while(true) { 79 | // Try to pick a task 80 | cout << "Locking: " << pthread_self() << endl; 81 | m_task_mutex.lock(); 82 | 83 | // We need to put pthread_cond_wait in a loop for two reasons: 84 | // 1. There can be spurious wakeups (due to signal/ENITR) 85 | // 2. When mutex is released for waiting, another thread can be waken up 86 | // from a signal/broadcast and that thread can mess up the condition. 87 | // So when the current thread wakes up the condition may no longer be 88 | // actually true! 89 | while ((m_pool_state != STOPPED) && (m_tasks.empty())) { 90 | // Wait until there is a task in the queue 91 | // Unlock mutex while wait, then lock it back when signaled 92 | cout << "Unlocking and waiting: " << pthread_self() << endl; 93 | m_task_cond_var.wait(m_task_mutex.get_mutex_ptr()); 94 | cout << "Signaled and locking: " << pthread_self() << endl; 95 | } 96 | 97 | // If the thread was woken up to notify process shutdown, return from here 98 | if (m_pool_state == STOPPED) { 99 | cout << "Unlocking and exiting: " << pthread_self() << endl; 100 | m_task_mutex.unlock(); 101 | pthread_exit(NULL); 102 | } 103 | 104 | task = m_tasks.front(); 105 | m_tasks.pop_front(); 106 | cout << "Unlocking: " << pthread_self() << endl; 107 | m_task_mutex.unlock(); 108 | 109 | //cout << "Executing thread " << pthread_self() << endl; 110 | // execute the task 111 | (*task)(); // could also do task->run(arg); 112 | //cout << "Done executing thread " << pthread_self() << endl; 113 | delete task; 114 | } 115 | return NULL; 116 | } 117 | 118 | int ThreadPool::add_task(Task* task) 119 | { 120 | m_task_mutex.lock(); 121 | 122 | // TODO: put a limit on how many tasks can be added at most 123 | m_tasks.push_back(task); 124 | 125 | m_task_cond_var.signal(); // wake up one thread that is waiting for a task to be available 126 | 127 | m_task_mutex.unlock(); 128 | 129 | return 0; 130 | } 131 | -------------------------------------------------------------------------------- /ThreadPool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "Mutex.h" 12 | #include "Task.h" 13 | #include "CondVar.h" 14 | #include "Global.h" 15 | 16 | using namespace std; 17 | 18 | 19 | class ThreadPool 20 | { 21 | public: 22 | ThreadPool(); 23 | ThreadPool(int pool_size); 24 | ~ThreadPool(); 25 | int initialize_threadpool(); 26 | int destroy_threadpool(); 27 | void* execute_thread(); 28 | int add_task(Task* task); 29 | private: 30 | int m_pool_size; 31 | Mutex m_task_mutex; 32 | CondVar m_task_cond_var; 33 | std::vector m_threads; // storage for threads 34 | std::deque m_tasks; 35 | volatile int m_pool_state; 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /bin/example/threadpool_test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilash/threadpool/fcde1593b86fb9ca53503d716a646c2a2b5bc4ce/bin/example/threadpool_test -------------------------------------------------------------------------------- /bin/examplethreadpool_test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilash/threadpool/fcde1593b86fb9ca53503d716a646c2a2b5bc4ce/bin/examplethreadpool_test -------------------------------------------------------------------------------- /threadpool_test.cpp: -------------------------------------------------------------------------------- 1 | #include "ThreadPool.h" 2 | 3 | //#include "threadpool.h" 4 | 5 | #include 6 | 7 | using namespace std; 8 | 9 | const int MAX_TASKS = 4; 10 | 11 | void hello(void* arg) 12 | { 13 | int* x = (int*) arg; 14 | cout << "Hello " << *x << endl; 15 | // cout << "\n"; 16 | } 17 | 18 | int main(int argc, char* argv[]) 19 | { 20 | ThreadPool tp(2); 21 | int ret = tp.initialize_threadpool(); 22 | if (ret == -1) { 23 | cerr << "Failed to initialize thread pool!" << endl; 24 | return 0; 25 | } 26 | 27 | for (int i = 0; i < MAX_TASKS; i++) { 28 | int* x = new int(); 29 | *x = i+1; 30 | Task* t = new Task(&hello, (void*) x); 31 | // cout << "Adding to pool, task " << i+1 << endl; 32 | tp.add_task(t); 33 | // cout << "Added to pool, task " << i+1 << endl; 34 | } 35 | 36 | sleep(2); 37 | 38 | tp.destroy_threadpool(); 39 | 40 | // TODO: delete worker objects 41 | 42 | cout << "Exiting app..." << endl; 43 | 44 | return 0; 45 | } 46 | --------------------------------------------------------------------------------