ThreadPoolLib is a minimalist C++ thread pool library, designed with simplicity and efficiency in mind. It provides an easy and straightforward way to manage and execute tasks concurrently in a multi-threaded environment, and is an essential tool for developers working on high-performance applications.
-
Ease of Use: ThreadPoolLib allows developers to easily add tasks with callbacks to be processed concurrently, through a simple and intuitive interface.
-
Efficiency: ThreadPoolLib leverages modern C++ features and best practices to ensure that your tasks are processed as efficiently as possible. It uses rvalue references and move semantics to avoid unnecessary copies, and manages all thread-related resources for you to eliminate the risk of common threading pitfalls such as zombie threads.
-
Performance: The library utilizes C++
std::thread
to create and manage threads. It optimizes the number of threads created based on the number of cores available, ensuring optimal utilization of hardware resources.
- Use of
std::unique_lock
: To provide a finer-grained control over locking and synchronization, the library has been updated to usestd::unique_lock
. This provides greater flexibility compared to the previousstd::lock_guard
, allowing for unlocking and re-locking a mutex within the same scope. This flexibility is especially useful in facilitating lengthy operations without holding the lock, thus reducing contention and enhancing performance. The implementation now leveragesstd::condition_variable
and itswait()
method, which puts threads to sleep when there are no tasks to process, rather than constantly polling for new tasks. This helps to reduce CPU usage. Additionally, the introduction of a predicate function withwait()
, manages the wake-up conditions for the sleeping threads, ensuring that they only awake when there are tasks to be consumed. This results in a more efficient and responsive system that optimizes task handling and thread utilization.
Instead of simply adding and running tasks concurrently, ThreadPoolLib supports advanced use cases through task callbacks and result handling. Here's what you can do:
-
Task Callbacks: Add a callback function that will be invoked once a task has completed its execution. This can be useful for handling results, logging, or chaining tasks.
-
Result Handling: When a task finishes execution, it might return a result. With ThreadPoolLib, you can easily handle these results within the callback function, allowing you to use or store the outcome of the task.
This library is under active development and future enhancements include:
- Thread Metrics and Visualization: In the interest of providing valuable insights about the efficiency of the tasks and the overall performance of the applications, I am planning to introduce a feature that collects and exposes thread metrics. These metrics will enable users to visualize and analyze the behavior of their thread pools over time using tools such as Grafana and InfluxDB.
- Task dependency: (suggested by @tugrul512bit here It will consist in creating dependencies between tasks, so one task can not be consumed until all of its dependencies are resolved, generating this way graphs of tasks, such as:
task 1 <--- (task 2 + task 3)
|
|
V
task 4 + task 5 ----> task 6
This project uses cmake
as a build system.
mkdir build && cd build
cmake ..
make
Include include/ThreadPool.h
and create a ThreadPool
object by specifying the number of threads you want to use. Then you can add tasks using the overloaded CreateTask()
method from the pool object. The Examples
mentioned here refers to a namespace containing some trivial sample functions and methods.
// Create a unique pointer to the pool
std::unique_ptr<ThreadPool> pool = std::make_unique<ThreadPool>(std::thread::hardware_concurrency());
// Shared pointer for the tasks:
std::shared_ptr<Task> task1, task2, task3, task4;
- Example 1:
task1 = pool->CreateTask(normalFunction, normalCallback);
- Example 2:
task2 = pool->CreateTask(
[](){
printf("\n Lambda main function \n");
},
[]() {
printf("\n Lambda callback \n");
}
);
- Example 3:
std::tuple<int, int, int> args = std::make_tuple(2, 555, 999);
task3 = pool->CreateTask(normalFunctionParams, normalCallbackParams, args);
- Example 4:
Examples::Foo fooObj1;
auto fooObj2 = std::make_shared<Examples::Foo>();
Task task4;
task4(
[&fooObj1, &fooObj2]() {
printf("Lambda Task %i", fooObj1.MyTask(9));
fooObj2->MyTask(1399);
},
[&fooObj1](){
fooObj1.MyCallback(fooObj1.MyTask(4 * 2));
}
);
pool.AddTask(std::move(task4));
- Note: The testing mode is enabled in the
CMakeLists.txt
file. To use the library without the testing mode, simply comment out the line:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG")
To run this code using Docker, you first need to build the Docker images and then run the container. Here are the necessary steps:
- First, build the base image with the following command:
docker build -t base-image -f docker/base/Dockerfile .
- Then, build the application image with the following command:
docker build -t threadlibpool-image -f docker/app/Dockerfile .
- Finally, run the application in a Docker container with the following command:
docker build -t threadlibpool-image -f docker/app/Dockerfile .
This project is licensed under the GPL 3 License - see the LICENSE.md file for details.