Планировщик задач на C++: создаём Task Scheduler с нуля

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Планировщик задач на C++: архитектура, реализация и примеры

Планировщик задач (Task Scheduler) — один из ключевых компонентов современных высоконагруженных систем, игровых движков, серверов приложений и embedded-устройств. В этой статье мы подробно разберём, как создать собственный планировщик задач на C++ с нуля, используя стандартную библиотеку языка. Вы узнаете, как устроена очередь задач, как работают таймеры, как синхронизировать потоки и избежать типичных ошибок.

Зачем нужен планировщик задач?

Планировщик задач позволяет:

  • асинхронно выполнять код в фоновых потоках;
  • задавать задержки и периодические запуски;
  • управлять приоритетами и порядком выполнения;
  • оптимизировать использование ресурсов CPU.

В отличие от простого std::thread, планировщик даёт гибкий контроль над выполнением и позволяет избежать создания потоков под каждую задачу.

Базовая архитектура

Любой планировщик состоит из трёх основных частей:

  • Очередь задач — хранит объекты задач (функции, аргументы, метаданные);
  • Пул потоков — один или несколько рабочих потоков, которые извлекают задачи из очереди и выполняют их;
  • Таймер / планировщик времени — позволяет откладывать выполнение или запускать задачи по расписанию.

Реализация очереди задач на C++

Для начала определим структуру задачи:

struct Task {
    std::function<void()> func;
    std::chrono::steady_clock::time_point executeAt;
    size_t id;
};

Очередь должна быть потокобезопасной. Используем std::priority_queue (чтобы задачи с меньшим временем выполнения извлекались первыми) и std::mutex для синхронизации:

#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <chrono>
#include <thread>
#include <vector>

class TaskScheduler {
public:
    explicit TaskScheduler(size_t threadCount = std::thread::hardware_concurrency()) {
        for (size_t i = 0; i < threadCount; ++i) {
            workers.emplace_back([this] { workerLoop(); });
        }
    }

    ~TaskScheduler() {
        stop();
    }

    // Добавить задачу с задержкой
    template<typename F, typename... Args>
    void schedule(F&& f, Args&&... args, std::chrono::milliseconds delay = std::chrono::milliseconds(0)) {
        auto task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
        Task t{std::move(task), std::chrono::steady_clock::now() + delay, nextId++};
        {
            std::lock_guard<std::mutex> lock(mutex);
            queue.push(std::move(t));
        }
        cv.notify_one();
    }

    void stop() {
        {
            std::lock_guard<std::mutex> lock(mutex);
            stopping = true;
        }
        cv.notify_all();
        for (auto& worker : workers) {
            if (worker.joinable()) worker.join();
        }
    }

private:
    struct Task {
        std::function<void()> func;
        std::chrono::steady_clock::time_point executeAt;
        size_t id;

        bool operator>(const Task& other) const {
            return executeAt > other.executeAt;
        }
    };

    void workerLoop() {
        while (true) {
            std::unique_lock<std::mutex> lock(mutex);
            cv.wait(lock, [this] { return stopping || !queue.empty(); });
            if (stopping && queue.empty()) return;

            if (!queue.empty()) {
                auto top = queue.top();
                if (std::chrono::steady_clock::now() >= top.executeAt) {
                    queue.pop();
                    lock.unlock();
                    top.func();
                } else {
                    // Ждём до времени выполнения задачи
                    cv.wait_until(lock, top.executeAt);
                }
            }
        }
    }

    std::priority_queue<Task, std::vector<Task>, std::greater<Task>> queue;
    std::mutex mutex;
    std::condition_variable cv;
    std::vector<std::thread> workers;
    std::atomic<bool> stopping{false};
    std::atomic<size_t> nextId{0};
};

Пример использования

Теперь создадим планировщик и добавим несколько задач с разными задержками:

#include <iostream>
#include <chrono>
#include <thread>

void taskA() {
    std::cout << "Task A executed at " << std::chrono                    

Рекомендаций