How to Initialize spdlog's Thread Pool with Custom Settings
You initialize spdlog's thread pool by constructing a spdlog::details::thread_pool object with your desired queue capacity, worker thread count, and optional start/stop callbacks, then pass this instance to the async_logger constructor to handle log dispatching.
spdlog's asynchronous logging architecture decouples log production from consumption using a dedicated thread pool that drains messages from a lock-free queue. When you need to tune performance for high-throughput applications or manage thread-specific resources, you must initialize spdlog's thread pool with custom settings rather than relying on defaults. This guide explains how to construct and configure spdlog::details::thread_pool using the actual implementation from the gabime/spdlog repository.
Understanding the Thread Pool Architecture
The thread pool in spdlog is represented by the spdlog::details::thread_pool class, declared in include/spdlog/details/thread_pool.h and implemented in include/spdlog/details/thread_pool-inl.h. This pool manages a configurable number of worker threads that run in a continuous loop, pulling log messages from a lock-free MPMC (multi-producer, multi-consumer) queue and forwarding them to the logger's sinks.
Each worker thread executes the private method worker_loop_(), which repeatedly calls process_next_msg_() to handle log, flush, or terminate messages. Because the pool is a standard C++ object, you control its lifetime and can share a single instance across multiple async loggers, ensuring your configuration outlives any logger that uses it.
Configurable Thread Pool Parameters
When constructing a custom thread pool, you specify four key settings that control memory usage and threading behavior:
- Queue capacity (
q_max_items): Defines the maximum number of pending log messages the internal lock-free queue can hold before blocking or overflowing. Default is 8192 items if you use implicit construction. - Number of worker threads (
threads_n): Determines how many threads executeworker_loop_()to process queued messages. Default is 1, with validation restricting values between 1 and 1000. - Thread start callback: A
std::function<void()>executed in each worker thread before it begins processing messages. Use this for thread-local initialization such as setting locale or thread naming. - Thread stop callback: A
std::function<void()>executed after a worker thread exits, useful for cleanup of thread-local resources.
Thread Pool Constructor Signatures
The thread_pool class provides three constructor overloads in include/spdlog/details/thread_pool.h to accommodate different configuration needs:
// Full configuration with callbacks
thread_pool(size_t q_max_items,
size_t threads_n,
std::function<void()> on_thread_start,
std::function<void()> on_thread_stop);
// Configuration with start callback only
thread_pool(size_t q_max_items,
size_t threads_n,
std::function<void()> on_thread_start);
// Basic configuration without callbacks
thread_pool(size_t q_max_items,
size_t threads_n);
The constructor implementation in thread_pool-inl.h validates that threads_n falls within the 1-1000 range, then spawns the requested number of std::thread objects in a loop (lines 26-32). Each thread immediately begins executing the worker_loop_() method.
Step-by-Step Initialization Process
Follow these steps to create and activate a custom thread pool for your async loggers:
- Determine queue size: Calculate based on your burst logging patterns. Larger queues (e.g., 32768 items) prevent blocking when producers outpace consumers but consume more memory.
- Set thread count: Match to your CPU topology and sink requirements. Typical deployments use 1-4 threads per CPU core, depending on sink latency.
- Define lifecycle callbacks: Implement start/stop functions if your sinks require per-thread setup (e.g., database connection pools, locale settings).
- Instantiate the pool: Create the
thread_poolobject on the heap or as a global to ensure it outlives your loggers. - Attach to logger: Pass the pool pointer to
spdlog::async_loggerorspdlog::create_async_logger.
The pool automatically cleans up when its destructor runs, sending a terminate message to each worker thread (see ~thread_pool implementation in thread_pool-inl.h lines 43-55).
Code Examples
Basic Custom Pool Configuration
This example creates a thread pool with 16,384 queue slots and 2 worker threads, then attaches it to a file logger:
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <memory>
int main()
{
// 16,384 message capacity, 2 worker threads, no callbacks
auto pool = std::make_shared<spdlog::details::thread_pool>(16384, 2);
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("mylog.txt", true);
auto logger = std::make_shared<spdlog::async_logger>(
"my_async_logger",
spdlog::sinks_init_list{file_sink},
pool, // custom thread pool injection
spdlog::thread_pool::default_queue_overflow_policy,
spdlog::async_overflow_policy::block);
spdlog::register_logger(logger);
logger->info("Hello from a custom thread pool!");
}
Thread Pool with Start/Stop Callbacks
Use callbacks to initialize thread-local resources, such as locale settings for proper UTF-8 handling:
auto start_cb = []{
std::locale::global(std::locale("en_US.UTF-8"));
};
auto stop_cb = []{
// Thread-local cleanup if needed
};
auto pool = std::make_shared<spdlog::details::thread_pool>(
32768, // queue capacity
4, // 4 worker threads
start_cb, // executed on thread start
stop_cb); // executed on thread exit
Global Thread Pool Pattern
For applications with multiple async loggers, initialize a single global pool shared across all loggers to conserve resources:
// Global pool living for the entire application lifetime
static std::shared_ptr<spdlog::details::thread_pool> g_pool =
std::make_shared<spdlog::details::thread_pool>(
8192,
std::thread::hardware_concurrency());
int main()
{
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto logger = std::make_shared<spdlog::async_logger>(
"global_async",
spdlog::sinks_init_list{console_sink},
g_pool, // shared pool
spdlog::thread_pool::default_queue_overflow_policy,
spdlog::async_overflow_policy::block);
spdlog::register_logger(logger);
logger->debug("Using the global async thread pool");
}
Key Implementation Files
include/spdlog/details/thread_pool.h: Declares thethread_poolclass, constructors, and public API.include/spdlog/details/thread_pool-inl.h: Contains inline definitions for thread creation, theworker_loop_()method, message processing, and termination logic.include/spdlog/async.h: Exposes the public async logger API and helper functions for creating loggers with custom pools.src/async.cpp: Provides glue code that connects async loggers to user-provided thread pools.
Summary
- Create a
spdlog::details::thread_poolinstance with explicitq_max_itemsandthreads_nparameters to override defaults (8192 items, 1 thread). - Provide optional start/stop callbacks in the constructor for thread-local resource management.
- Pass the thread pool pointer to
spdlog::async_loggerconstructors; ensure the pool outlives its associated loggers. - Worker threads automatically shut down cleanly via the destructor, which sends terminate messages to the
worker_loop_(). - Reference
thread_pool.hfor interface definitions andthread_pool-inl.hfor implementation details including thread validation (1-1000 threads) and message processing loops.
Frequently Asked Questions
What is the default queue size for spdlog's thread pool?
The default queue capacity is 8192 messages, defined by the spdlog::details::mpmc_blocking_queue defaults. If you create an async logger without specifying a custom thread pool, spdlog uses this default capacity. For high-throughput applications, increase this to 16384 or 32768 to reduce producer blocking.
How many threads should I allocate to spdlog's thread pool?
Allocate 1 to 4 worker threads per CPU core, depending on your sink latency and throughput requirements. The constructor validates that thread counts fall between 1 and 1000. More threads increase parallelism but add CPU overhead and context switching costs; start with std::thread::hardware_concurrency() and tune based on performance metrics.
Can I change thread pool settings after creating the async logger?
No, thread pool settings are immutable after construction. The thread_pool class does not provide methods to resize the queue or adjust thread count at runtime. If you need different settings, you must create a new thread pool and new async loggers using that pool, then retire the old loggers.
What happens if the thread pool queue becomes full?
When the queue reaches q_max_items capacity, the behavior depends on the async overflow policy you specified when creating the logger. With spdlog::async_overflow_policy::block, the caller blocks until space is available. With async_overflow_policy::discard, new log messages are discarded. Choose your queue size and policy based on your application's latency and data loss requirements.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →