# How to Initialize spdlog's Thread Pool with Custom Settings

> Learn to initialize spdlog's thread pool with custom settings. Configure queue capacity, worker count, and callbacks for efficient asynchronous logging. Maximize your application's performance.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: how-to-guide
- Published: 2026-07-22

---

**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`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool.h) and implemented in [`include/spdlog/details/thread_pool-inl.h`](https://github.com/gabime/spdlog/blob/main/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`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool.h) to accommodate different configuration needs:

```cpp
// 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`](https://github.com/gabime/spdlog/blob/main/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`](https://github.com/gabime/spdlog/blob/main/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:

```cpp
#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:

```cpp
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:

```cpp
// 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`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool.h)**: Declares the `thread_pool` class, constructors, and public API.
- **[`include/spdlog/details/thread_pool-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool-inl.h)**: Contains inline definitions for thread creation, the `worker_loop_()` method, message processing, and termination logic.
- **[`include/spdlog/async.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async.h)**: Exposes the public async logger API and helper functions for creating loggers with custom pools.
- **[`src/async.cpp`](https://github.com/gabime/spdlog/blob/main/src/async.cpp)**: Provides glue code that connects async loggers to user-provided thread pools.

## 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`](https://github.com/gabime/spdlog/blob/main/thread_pool.h) for interface definitions and [`thread_pool-inl.h`](https://github.com/gabime/spdlog/blob/main/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.