Skip to content

xjn2005/Thread-Pool

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Thread Pool

中文说明

A small but practical C++17 thread pool implementation with:

  • fixed worker threads
  • std::future-based task submission
  • safe shutdown and join on destruction
  • CMake build
  • lightweight unit tests via ctest

Features

  • C++17 interface
  • Submit() accepts arbitrary callables and returns std::future
  • WaitIdle() waits until the queue is empty and all active tasks are finished
  • Shutdown() prevents new tasks and joins all worker threads
  • falls back to one worker when thread_count == 0

Project Layout

thread-pool/
├── CMakeLists.txt
├── README.md
├── include/
│   └── thread_pool/
│       └── thread_pool.h
├── src/
│   └── thread_pool.cc
├── examples/
│   └── main.cpp
└── tests/
    └── thread_pool_test.cpp

Build And Test

Recommended environment: WSL Ubuntu + CMake + Ninja + g++

In WSL

cd ~/projects/thread-pool
cmake -S . -B build -G Ninja
cmake --build build
ctest --test-dir build --output-on-failure

Run the example:

./build/thread_pool_example

On Windows With MSVC

The code itself is portable, but in this workspace the Windows-side CMake build directory had permission issues. If you want to build on Windows, prefer:

  • source directory on a normal writable path
  • a separate build directory outside the source tree
  • Developer PowerShell or vcvars64.bat

Example:

cmake -S D:\projects\thread-pool -B C:\Users\pc\build\thread-pool -G Ninja -DCMAKE_CXX_COMPILER=cl
cmake --build C:\Users\pc\build\thread-pool
ctest --test-dir C:\Users\pc\build\thread-pool --output-on-failure

Public API

Header: include/thread_pool/thread_pool.h

thread_pool::ThreadPool pool(4);

auto future = pool.Submit([](int x, int y) {
  return x + y;
}, 1, 2);

pool.WaitIdle();
int result = future.get();
pool.Shutdown();

ThreadPool(std::size_t thread_count)

Creates a fixed-size worker pool. If thread_count is 0, it creates one worker thread.

Submit(Function&& function, Args&&... args)

Packages the callable and its arguments into a task, pushes it into the queue, and returns a std::future for the result.

If the pool has already been shut down, this throws std::runtime_error.

WaitIdle()

Blocks until:

  • the task queue is empty
  • no worker is currently executing a task

Shutdown()

Marks the pool as stopping, wakes all worker threads, and joins them.

Shutdown() is idempotent. The destructor also calls it automatically.

Test Coverage

The current test target checks:

  • submitted tasks return expected values
  • WaitIdle() waits for queued work
  • exceptions inside tasks are propagated through future.get()
  • submitting after shutdown throws
  • zero worker count falls back to one thread

Run tests with:

ctest --test-dir build --output-on-failure

Design Highlights

1. Generic task submission

Submit() supports arbitrary callables and arguments and uses:

  • std::invoke_result_t
  • std::packaged_task
  • std::future
  • std::apply

That gives a clean and type-safe interface without forcing callers into std::function<...> signatures.

2. Clear worker lifecycle

Workers block on a condition variable, pop one task at a time, execute it, then update the active worker count.

The shutdown rule is simple:

  • if stopping and queue empty, worker exits
  • otherwise remaining queued tasks are drained before exit

3. Explicit idle waiting

Many small thread pool examples omit this. WaitIdle() is useful in tests and batch-style workloads where you want a precise synchronization point before reading results or shutting down external state.

Main Difficulties

1. Correct shutdown semantics

This is the most error-prone part of a thread pool.

You have to define:

  • whether shutdown drops queued tasks or drains them
  • whether new submission is allowed during shutdown
  • when worker threads are allowed to exit

This project chooses:

  • no new submissions after shutdown
  • queued tasks are drained
  • worker threads exit only when stopping is true and the queue is empty

2. Avoiding race conditions around idle detection

WaitIdle() is only correct if both conditions are tracked:

  • queued task count
  • currently running worker count

Checking only whether the queue is empty is wrong, because a worker may already have popped a task and still be executing it.

3. Exception propagation

If a task throws and the worker thread does not contain that exception, the program can terminate.

Using std::packaged_task solves this cleanly because exceptions are captured and rethrown at future.get().

4. Template interface placement

Submit() is a template, so it must stay in the header unless you redesign the API. This is a common C++ library design constraint and easy to get wrong when splitting .h and .cc.

Possible Extensions

  • bounded task queue
  • StopNow() that drops queued work
  • task priority support
  • dynamic thread resizing
  • timeout-aware waiting
  • replacing the custom test harness with GoogleTest

Current Status

This project has been verified successfully in WSL:

  • cmake configure: passed
  • cmake --build: passed
  • ctest: passed

About

A small but practical C++17 thread pool implementation

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors