Планировщик задач на C++: создание и управление задачами

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

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

Начать курс

Планировщик задач на C++: полное руководство

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

Основные компоненты планировщика

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

  • Хранилище задач — очередь или контейнер для хранения запланированных задач.
  • Таймер — механизм, определяющий, когда должна быть выполнена задача.
  • Пул потоков — для параллельного выполнения задач без блокировки основного потока.
  • Механизм отмены — возможность отменить задачу до её выполнения.

Простая реализация планировщика с одним потоком

Начнём с базового примера, где задачи выполняются последовательно в фоновом потоке:

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

class TaskScheduler {
public:
    // Планирует задачу с задержкой
    void schedule(std::function<void()> task, int delay_ms) {
        std::lock_guard<std::mutex> lock(mutex_);
        tasks_.push({std::chrono::steady_clock::now() + 
                     std::chrono::milliseconds(delay_ms), task});
        cv_.notify_one();
    }

    // Запускает планировщик в отдельном потоке
    void start() {
        worker_ = std::thread([this]() {
            while (!stop_) {
                std::unique_lock<std::mutex> lock(mutex_);
                if (tasks_.empty()) {
                    cv_.wait(lock, [this]() { 
                        return !tasks_.empty() || stop_; 
                    });
                }
                if (stop_) break;

                auto now = std::chrono::steady_clock::now();
                auto& top = tasks_.top();
                if (top.time <= now) {
                    auto task = top.task;
                    tasks_.pop();
                    lock.unlock();
                    task(); // Выполняем задачу
                } else {
                    cv_.wait_until(lock, top.time);
                }
            }
        });
    }

    void stop() {
        stop_ = true;
        cv_.notify_all();
        if (worker_.joinable()) worker_.join();
    }

private:
    struct Task {
        std::chrono::steady_clock::time_point time;
        std::function<void()> task;
        bool operator>(const Task& other) const {
            return time > other.time;
        }
    };

    std::priority_queue<Task, std::vector<Task>, std::greater<Task>> tasks_;
    std::mutex mutex_;
    std::condition_variable cv_;
    std::thread worker_;
    bool stop_ = false;
};

int main() {
    TaskScheduler scheduler;
    scheduler.start();

    scheduler.schedule([]() {
        std::cout << "Задача 1 выполнена через 2 секунды\n";
    }, 2000);

    scheduler.schedule([]() {
        std::cout << "Задача 2 выполнена через 1 секунду\n";
    }, 1000);

    std::this_thread::sleep_for(std::chrono::seconds(3));
    scheduler.stop();
    return 0;
}

Планировщик с пулом потоков для параллельного выполнения

Для более сложных сценариев, когда задачи могут выполняться долго, лучше использовать пул потоков. Вот пример с минимальным пулом:

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

class ThreadPool {
public:
    ThreadPool(size_t threads) : stop_(false) {
        for (size_t i = 0; i < threads; ++i) {
            workers_.emplace_back([this]() {
                while (true) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(queue_mutex_);
                        condition_.wait(lock, [this]() {
                            return stop_ || !tasks_.empty();
                        });
                        if (stop_ && tasks_.empty()) return;
                        task = std::move(tasks_.front());
                        tasks_.pop();
                    }
                    task();
                }
            });
        }
    }

    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) 
        -> std::future<typename std::result_of<F(Args...)>::type> {
        using return_type = typename std::result_of<F(Args...)>::type;
        auto task = std::make_shared<std::packaged_task<return_type()>>(
            std                    

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