[
](LICENSE)
A modern C++ library for advanced thread management on Linux and Windows. ThreadSchedule provides enhanced wrappers for std::thread, std::jthread, and pthread with extended functionality including thread naming, priority management, CPU affinity, and high-performance thread pools.
Available as header-only, as a C++20 module (import threadschedule;), or with optional shared runtime for multi-DSO applications.
Key Features
- Modern C++: Full C++17, C++20, C++23, and C++26 support with automatic feature detection and optimization
- C++20 Modules: Optional import threadschedule; support (C++20+)
- Header-Only or Shared Runtime: Choose based on your needs
- Enhanced Wrappers: Extend std::thread, std::jthread, and pthread with powerful features
- Non-owning Views: Zero-overhead views to configure existing threads or find by name (Linux)
- Thread Naming: Human-readable thread names for debugging
- Priority & Scheduling: Fine-grained control over thread priorities and scheduling policies
- CPU Affinity: Pin threads to specific CPU cores
- Global Control Registry: Process-wide registry to list and control running threads (affinity, priority, name)
- Profiles: High-level presets for priority/policy/affinity
- NUMA-aware Topology Helpers: Easy affinity builders across nodes
- Chaos Testing: RAII controller to perturb affinity/priority for validation
- C++20 Coroutines: task<T>, generator<T>, and sync_wait out of the box - no boilerplate promise types needed
- High-Performance Pools: Work-stealing pool, post() / try_post(), and optional LightweightPool for fire-and-forget workloads with minimal overhead
- Scheduled Tasks: Run tasks at specific times, after delays, or periodically
- Error Handling: Comprehensive exception handling with error callbacks and context
- Performance Metrics: Built-in statistics and monitoring
- RAII & Exception Safety: Automatic resource management
- Multiple Integration Methods: CMake, CPM, Conan, FetchContent
What's new in v2.0
Version 2.0 focuses on lower-overhead submission, more control over shutdown and tuning, and better ergonomics for modern C++ (ranges, coroutines, std::stop_token). Highlights:
| Area | What changed |
| Lightweight pool | LightweightPoolT<TaskSize> / LightweightPool - fire-and-forget only, configurable SBO buffer (default 64 B), no futures or stats. Workers are still ThreadWrapper (name, affinity, policy). Ideal for maximum throughput when you do not need a return value. |
| post() / try_post() | On HighPerformancePool, ThreadPool / FastThreadPool, and GlobalPool - same queue path as submit() but skips packaged_task / future overhead. |
| Non-throwing submit | try_submit() returns expected<future<R>, error_code>; try_submit_batch() returns expected<vector<future<void>>, error_code> instead of throwing on shutdown. |
| Scheduled dispatch | ScheduledThreadPoolT dispatches with post() internally. Alias ScheduledLightweightPool uses LightweightPool as the backend. |
| Shutdown | ShutdownPolicy::drain (default) vs drop_pending; shutdown_for(timeout) for a timed drain. |
| Parallel loops | Chunked parallel_for_each on all pool types (shared helper across single-queue and work-stealing pools). |
| Tuning | PollingWait<IntervalMs> for FastThreadPool, configurable work-stealing deque capacity on HighPerformancePool, GlobalPool::init(n) before first use. |
| C++20 | Ranges overloads for batch submit and parallel_for_each; submit/try_submit with std::stop_token (cooperative skip). |
| Futures | when_all, when_any, when_all_settled in futures.hpp. |
| Coroutines | schedule_on{pool}, pool_executor, run_on(pool, coro_fn) for pool-aware task. |
| Observability | Optional auto-registration of pool workers in the thread registry; per-task set_on_task_start / set_on_task_end hooks. |
| Errors | ErrorHandler callbacks get stable IDs; remove_callback(id) / has_callback(id). |
See CHANGELOG.md for the full list, including breaking changes when upgrading from v1.x.
Upgrading from v1.x: Migration guide (v2.0)
Documentation
Platform Support
ThreadSchedule is designed to work on any platform with a C++17 (or newer) compiler and standard threading support. The library is continuously tested on:
| Platform | Compiler | C++17 | C++20 | C++23 | C++26 |
| Linux (x86_64) | | | | | |
| Ubuntu 22.04 | GCC 11 | yes | yes | yes | - |
| Ubuntu 22.04 | GCC 12 | - | yes | - | - |
| Ubuntu 22.04 | Clang 14 | yes | yes | yes | - |
| Ubuntu 22.04 | Clang 15 | - | yes | yes | - |
| Ubuntu 24.04 | GCC 13 | yes | yes | yes | - |
| Ubuntu 24.04 | GCC 14 | yes | yes | yes | yes |
| Ubuntu 24.04 | GCC 15 | - | yes | yes | yes |
| Ubuntu 24.04 | Clang 16 | yes | yes | - | - |
| Ubuntu 24.04 | Clang 18 | yes | yes | - | - |
| Ubuntu 24.04 | Clang 19 | - | yes | yes | yes |
| Ubuntu 24.04 | Clang 21 | - | yes | yes | yes |
| Linux (ARM64) | | | | | |
| Ubuntu 24.04 ARM64 | GCC 13 (system) | yes | yes | yes | - |
| Ubuntu 24.04 ARM64 | GCC 14 | - | yes | yes | yes |
| Windows | | | | | |
| Windows Server 2022 | MSVC 2022 | yes | yes | yes | - |
| Windows Server 2022 | MinGW-w64 (GCC 15) | yes | yes | yes | - |
| Windows Server 2025 | MSVC 2022 | yes | yes | yes | - |
| Windows Server 2025 | MinGW-w64 (GCC 15) | yes | yes | yes | - |
Additional platforms: ThreadSchedule should work on other platforms (macOS, FreeBSD, other Linux distributions) with standard C++17+ compilers, but these are not regularly tested in CI.
C++23: GCC 12's libstdc++ lacks monadic std::expected operations (and_then, transform, ...). Clang 16/18 on Ubuntu 24.04 use GCC 14's libstdc++ headers which expose std::expected incorrectly to those Clang versions. These combinations are therefore only tested up to C++20.
C++26: Requires GCC 14+ or Clang 19+. MSVC does not yet expose cxx_std_26 to CMake; C++26 on Windows is not tested.
GCC 15: Installed via ppa:ubuntu-toolchain-r/test on Ubuntu 24.04.
Clang 21: Installed via the official LLVM apt repository (apt.llvm.org) on Ubuntu 24.04.
Windows ARM64: Not currently covered by GitHub-hosted runners, requires self-hosted runner for testing.
MinGW: MinGW-w64 (MSYS2) ships GCC 15 and provides full Windows API support including thread naming (Windows 10+).
Quick Start
Installation
Add to your CMakeLists.txt using CPM.cmake:
include(${CMAKE_BINARY_DIR}/cmake/CPM.cmake)
CPMAddPackage(
NAME ThreadSchedule
GITHUB_REPOSITORY Katze719/ThreadSchedule
GIT_TAG main # or specific version tag
OPTIONS "THREADSCHEDULE_BUILD_EXAMPLES OFF" "THREADSCHEDULE_BUILD_TESTS OFF"
)
add_executable(your_app src/main.cpp)
target_link_libraries(your_app PRIVATE ThreadSchedule::ThreadSchedule)
Other integration methods: See docs/INTEGRATION.md for FetchContent, Conan, system installation, and shared runtime option.
C++20 Module Usage
ThreadSchedule can also be consumed as a C++20 module (requires CMake 3.28+ and Ninja or Visual Studio 17.4+):
# In your CMakeLists.txt
set(CMAKE_CXX_STANDARD 20)
CPMAddPackage(
NAME ThreadSchedule
GITHUB_REPOSITORY Katze719/ThreadSchedule
GIT_TAG main
OPTIONS "THREADSCHEDULE_MODULE ON"
)
add_executable(your_app src/main.cpp)
target_link_libraries(your_app PRIVATE ThreadSchedule::Module)
import threadschedule;
int main() {
ts::HighPerformancePool pool(4);
auto future = pool.submit([]() { return 42; });
return future.get() != 42;
}
Basic Usage
int main() {
std::cout << "Worker running!" << std::endl;
});
pool.configure_threads("worker");
pool.distribute_across_cpus();
auto future = pool.submit([]() { return 42; });
std::cout << "Result: " << future.get() << std::endl;
pool.post([]() { });
lite.configure_threads("lite");
lite.post([]() { });
auto handle = scheduler.schedule_periodic(std::chrono::seconds(5), []() {
std::cout << "Periodic task executed!" << std::endl;
});
auto handle_hp = scheduler_hp.schedule_periodic(std::chrono::milliseconds(100), []() {
std::cout << "Frequent task!" << std::endl;
});
pool_safe.add_error_callback([](
const TaskError& error) {
std::cerr <<
"Task error: " << error.
what() << std::endl;
});
return 0;
}
auto set_name(std::string const &name) -> expected< void, std::error_code >
static constexpr auto normal() noexcept -> ThreadPriority
Owning wrapper around std::thread with RAII join-on-destroy semantics.
ScheduledThreadPoolT< ThreadPool > ScheduledThreadPool
ScheduledThreadPoolT using the default ThreadPool backend.
PoolWithErrors< HighPerformancePool > HighPerformancePoolWithErrors
HighPerformancePool with integrated error handling.
ScheduledThreadPoolT< HighPerformancePool > ScheduledHighPerformancePool
ScheduledThreadPoolT using HighPerformancePool as backend.
LightweightPoolT<> LightweightPool
Default lightweight pool with 64-byte task slots (56 bytes usable).
Holds diagnostic information captured from a failed task.
auto what() const -> std::string
Extract the message string from the stored exception.
Modern C++17/20/23/26 Thread Scheduling Library.
Non-owning Thread Views
Operate on existing threads without owning their lifetime.
std::thread t([]{ });
v.set_name("worker_0");
v.join();
Manages a set of CPU indices to which a thread may be bound.
Non-owning view over an externally managed std::thread.
Using thread views with APIs expecting std::thread/stdjthread references
- Views do not own threads. Use .get() to pass a reference to APIs that expect std::thread& or (C++20) std::jthread&.
- Ownership stays with the original std::thread/std::jthread object.
void configure(std::thread& t);
std::thread t([]{ });
configure(v.get());
You can also pass threads directly to APIs that take views; the view is created implicitly (non-owning):
std::thread t2([]{});
operate(t2);
std::jthread (C++20):
std::jthread jt([](std::stop_token st){ });
jv.set_name("jworker");
jv.request_stop();
jv.join();
ThreadWrapperView JThreadWrapperView
Global Thread Registry
Opt-in registered threads with process-wide control, without imposing overhead on normal wrappers.
int main() {
});
});
.count();
auto tids =
registry().filter(...).map([](
auto& e) {
return e.
tid; });
t.join();
}
Value-semantic wrapper for a thread scheduling priority.
ThreadWrapper with automatic registration in the global ThreadRegistry.
auto registry() -> ThreadRegistry &
Returns a reference to the process-wide ThreadRegistry.
Thread wrappers with automatic global registry registration.
Snapshot of metadata for a single registered thread.
Process-wide thread registry, control blocks, and composite registry.
For multi-DSO applications: Use the shared runtime option (THREADSCHEDULE_RUNTIME=ON) to ensure a single process-wide registry. See docs/REGISTRY.md for detailed patterns.
Notes:
- Normal wrappers (ThreadWrapper, JThreadWrapper, PThreadWrapper) remain zero-overhead.
- The registry requires control blocks for all operations. Threads must be registered with control blocks to be controllable via the registry.
- Use *Reg wrappers (e.g., ThreadWrapperReg) or AutoRegisterCurrentThread for automatic control block creation and registration.
Find by name (Linux):
if (by_name.found()) {
by_name.set_name("new_name");
by_name.set_affinity(one_core);
}
Looks up an OS thread by its name via /proc and provides scheduling control.
Error handling with expected
ThreadSchedule uses threadschedule::expected<T, std::error_code> (and expected<void, std::error_code>). When available, this aliases to std::expected, otherwise, a compatible fallback based on P0323R3 is used.
Note: when building with -fno-exceptions, behavior is not standard-conforming because value()/operator* cannot throw bad_expected_access on error (exceptions are disabled). In that mode, always check has_value() or use value_or() before accessing the value.
Recommended usage:
auto r = worker.set_name("my_worker");
if (!r) {
}
auto value = pool.submit([]{ return 42; });
Coroutines (C++20)
Lazy coroutine primitives - no boilerplate promise types required.
task<int> compute(int x) {
co_return x * 2;
}
task<int> pipeline() {
int a = co_await compute(21);
co_return a;
}
int main() {
int result = sync_wait(pipeline());
auto fib = []() -> generator<int> {
int a = 0, b = 1;
while (true) {
co_yield a;
auto tmp = a; a = b; b = tmp + b;
}
};
for (int v : fib()) {
if (v > 1000) break;
std::cout << v << "\n";
}
}
For more details: See the Coroutines Guide.
API Overview
Thread Wrappers
| Class | Description | Available On |
| ThreadWrapper | Enhanced std::thread with naming, priority, affinity | Linux, Windows |
| JThreadWrapper | Enhanced std::jthread with cooperative cancellation (C++20) | Linux, Windows |
| PThreadWrapper | Modern C++ interface for POSIX threads | Linux only |
Passing wrappers into APIs expecting std::thread/stdjthread
- std::thread and std::jthread are move-only. When an API expects std::thread&& or std::jthread&&, pass the underlying thread via release() from the wrapper.
- Avoid relying on implicit conversions; release() clearly transfers ownership and prevents accidental selection of the functor constructor of std::thread.
void accept_std_thread(std::thread&& t);
accept_std_thread(w.release());
- Conversely, you can construct wrappers from rvalue threads:
std::thread make_thread();
take_wrapper(make_thread());
std::thread t([]{});
take_wrapper(std::move(t));
Thread Views (non-owning)
Zero-overhead helpers to operate on existing threads without taking ownership.
| Class | Description | Available On |
| ThreadWrapperView | View over an existing std::thread | Linux, Windows |
| JThreadWrapperView | View over an existing std::jthread (C++20) | Linux, Windows |
| ThreadByNameView | Locate and control a thread by its name | Linux only |
Thread Pools
| Class | Use Case | Notes |
| ThreadPool | Single shared queue, blocks while idle | submit, try_submit, post, batches, parallel_for_each |
| FastThreadPool | Same as ThreadPool with polling wait policy | Tunable via PollingWait<IntervalMs> |
| HighPerformancePool | Work-stealing + overflow queue | Highest throughput for large batches; tunable deque capacity |
| LightweightPool | Fire-and-forget only, SBO tasks | No futures; use post / post_batch. Alias of LightweightPoolT<64> |
All of the above support shutdown(ShutdownPolicy) and shutdown_for(timeout) where applicable. Use post() when you do not need a std::future (lower overhead than submit()).
Configuration
worker.set_affinity(affinity);
static constexpr auto lowest() noexcept -> ThreadPriority
@ OTHER
Standard round-robin time-sharing.
@ BATCH
For batch style execution.
@ IDLE
For very low priority background tasks.
@ FIFO
First in, first out.
For more details: See the Integration Guide, Registry Guide, and CMake Reference linked at the top of this README.
Benchmark Results
Performance varies by system configuration, workload characteristics, and task complexity. See benchmarks/ for detailed performance analysis, real-world scenario testing, and optimization recommendations.
Platform-Specific Features
Linux
- Full pthread API support
- Real-time scheduling policies (FIFO, RR, DEADLINE)
- CPU affinity and NUMA control
- Nice values for process priority
Windows
- Thread naming (Windows 10 1607+)
- Thread priority classes
- CPU affinity masking
- Process priority control
Note: PThreadWrapper is Linux-only. Use ThreadWrapper or JThreadWrapper for cross-platform code.
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (git checkout -b feature/amazing-feature)
- Commit your changes with clear messages
- Push to your branch (git push origin feature/amazing-feature)
- Open a Pull Request
License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
Acknowledgments
- POSIX threads documentation
- Modern C++ threading best practices
- Linux kernel scheduling documentation
- C++20/23/26 concurrency improvements