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 execute worker_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:

  1. 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.
  2. Set thread count: Match to your CPU topology and sink requirements. Typical deployments use 1-4 threads per CPU core, depending on sink latency.
  3. Define lifecycle callbacks: Implement start/stop functions if your sinks require per-thread setup (e.g., database connection pools, locale settings).
  4. Instantiate the pool: Create the thread_pool object on the heap or as a global to ensure it outlives your loggers.
  5. Attach to logger: Pass the pool pointer to spdlog::async_logger or spdlog::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

Summary

  • Create a spdlog::details::thread_pool instance with explicit q_max_items and threads_n parameters 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_logger constructors; 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.h for interface definitions and thread_pool-inl.h for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →