How to Use periodic_worker for Scheduled Tasks in spdlog

spdlog::details::periodic_worker is an RAII-style helper that runs a user-provided callback on a dedicated thread at fixed intervals, automatically managing thread lifecycle through a condition variable and mutex.

The spdlog logging library provides a lightweight concurrency primitive called periodic_worker for executing background tasks on a schedule. Located in the include/spdlog/details/ directory, this header-only component allows you to embed periodic execution logic directly into sinks or custom classes without manually managing thread lifecycle.

Understanding periodic_worker Design

RAII Thread Management

According to the source code in include/spdlog/details/periodic_worker.h, the class encapsulates a std::thread that remains suspended until the interval elapses. The constructor accepts a std::function<void()> callback and a std::chrono::duration interval. If the interval is greater than zero, the class immediately spawns a thread that loops indefinitely.

Condition Variable and Shutdown

The implementation in include/spdlog/details/periodic_worker-inl.h reveals that the worker uses a std::condition_variable named cv_ to block the thread. Inside the loop, cv_.wait_for pauses execution for the specified duration unless the active_ flag is cleared. When the destructor runs, it acquires the mutex, sets active_ to false, notifies the condition variable to break the wait, and joins the thread, ensuring clean shutdown without leaking resources.

Basic Usage Example

To create a simple timer that prints a message every second, instantiate spdlog::details::periodic_worker with a lambda and a duration:

#include <spdlog/details/periodic_worker.h>
#include <chrono>
#include <iostream>

int main() {
    // Print a message every second
    spdlog::details::periodic_worker ticker(
        []() { std::cout << "tick: " << std::chrono::system_clock::now().time_since_epoch().count() << '\n'; },
        std::chrono::seconds(1));

    // Let it run for 5 seconds
    std::this_thread::sleep_for(std::chrono::seconds(5));
    // `ticker` is destroyed here, thread stops cleanly
}

Embedding periodic_worker in Custom Classes

The class is designed to be used as a member variable. This example shows how to automatically flush a logger every 30 seconds:

#include <spdlog/details/periodic_worker.h>
#include <spdlog/spdlog.h>
#include <chrono>

class LogFlusher {
public:
    LogFlusher(std::shared_ptr<spdlog::logger> logger)
        : logger_(std::move(logger)),
          flusher_([this] { logger_->flush(); }, std::chrono::seconds(30)) {}

private:
    std::shared_ptr<spdlog::logger> logger_;
    spdlog::details::periodic_worker flusher_;   // runs logger_->flush() every 30s
};

Disabling the Worker with Zero Interval

If you pass a zero-length duration to the constructor, the worker becomes inactive and does not spawn a thread. This is useful for conditional periodic behavior:

spdlog::details::periodic_worker disabled_worker(
    [](){ /* never called */ },
    std::chrono::seconds(0));   // worker becomes inactive, no thread is started

Implementation Details

The header-only design means that when SPDLOG_HEADER_ONLY is defined, the implementation from include/spdlog/details/periodic_worker-inl.h is inlined directly into your translation unit. The worker stores the callback as a std::function<void()> and the interval as a std::chrono::duration, making it compatible with any time unit from milliseconds to hours.

Summary

  • spdlog::details::periodic_worker provides RAII-style periodic task execution in a header-only format.
  • The constructor accepts a std::function<void()> and a std::chrono::duration; passing a zero duration disables the worker.
  • The destructor safely stops the thread by clearing the active_ flag, notifying the condition variable, and joining the thread.
  • You can embed it as a member variable in sinks or utility classes to automatically run background tasks like log flushing or heartbeat messages.

Frequently Asked Questions

What happens if I pass a zero interval to periodic_worker?

When the interval is zero, the constructor sets the worker to inactive and does not spawn any thread. The callback is never invoked, and the destructor performs no thread operations, making it safe to use as a no-op placeholder.

Is periodic_worker thread-safe?

The class itself is thread-safe for construction and destruction from the owning thread. However, the callback function you provide must handle its own synchronization if it accesses shared data. The internal mutex and condition variable protect only the active_ state and thread lifecycle, not the contents of your callback.

How do I stop the periodic_worker manually?

There is no public stop() method. The worker follows strict RAII semantics and only stops when the object is destroyed. To temporarily pause execution, you must destroy the periodic_worker instance and recreate it later, or implement a custom active flag inside your callback logic.

Can I change the interval after construction?

No, the interval is fixed at construction time. To modify the timing, you must destroy the existing periodic_worker and create a new one with the desired interval. This design keeps the implementation minimal and lock-free for the common case.

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 →