Планировщик задач на Rust: создание надёжного job scheduler

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

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

Начать курс

Планировщик задач на Rust: от простого таймера до промышленного job scheduler

Планировщик задач (job scheduler) — один из ключевых компонентов любого серьёзного приложения. На Rust вы можете построить систему, которая будет выполняться с точностью до миллисекунд, потреблять минимум памяти и работать без сбоев годами. В этой статье мы разберём, как создать планировщик задач на Rust, используя как стандартные средства, так и популярные крейты.

Зачем писать планировщик на Rust?

Rust предлагает уникальное сочетание производительности и безопасности. Планировщик задач на Rust:

  • Гарантирует отсутствие гонок данных (data races) — компилятор проверяет это на этапе сборки.
  • Работает без сборщика мусора — предсказуемая задержка выполнения задач.
  • Поддерживает как асинхронное, так и многопоточное выполнение.
  • Имеет минимальный потребляемый объём памяти — можно запускать тысячи задач.

Простейший планировщик с std::thread::sleep

Начнём с базового примера — циклическое выполнение задачи каждые N секунд:

use std::thread;
use std::time::Duration;

fn main() {
    let interval = Duration::from_secs(5);
    
    loop {
        println!("[{}] Выполняю задачу...", chrono::Local::now().format("%H:%M:%S"));
        // Здесь ваш код задачи
        thread::sleep(interval);
    }
}

Этот подход прост, но неэффективен: блокируется текущий поток, нет возможности запускать несколько задач параллельно.

Асинхронный планировщик на tokio

Для реальных проектов используйте tokio — асинхронный рантайм, который позволяет запускать тысячи задач в одном потоке.

use tokio::time::{interval, Duration};

#[tokio::main]
async fn main() {
    let mut timer = interval(Duration::from_secs(10));
    
    println!("Планировщик запущен. Задача будет выполняться каждые 10 секунд.");
    
    loop {
        timer.tick().await;
        println!("[{}] Выполняю задачу...", chrono::Local::now().format("%H:%M:%S"));
        // Асинхронная задача: например, запрос к API
        do_work().await;
    }
}

async fn do_work() {
    // Имитация полезной работы
    tokio::time::sleep(Duration::from_millis(500)).await;
    println!("Задача завершена.");
}

Преимущества: не блокирует поток, можно параллельно запускать сотни таких планировщиков.

CRON-подобное расписание с крейтом cron

Если нужно задавать сложные расписания (как в Linux cron), используйте крейт cron:

use cron::Schedule;
use chrono::Utc;
use std::str::FromStr;

fn main() {
    // Каждую минуту в 30 секунд
    let expression = "30 * * * * *";
    let schedule = Schedule::from_str(expression).unwrap();
    
    println!("Следующие 5 запусков:");
    for datetime in schedule.upcoming(Utc).take(5) {
        println!("  {}", datetime);
    }
}

Вот более полный пример с реальным выполнением:

use cron::Schedule;
use chrono::{Utc, Local};
use std::thread;
use std::time::Duration;
use std::str::FromStr;

fn main() {
    // Каждые 15 секунд
    let expression = "0/15 * * * * *";
    let schedule = Schedule::from_str(expression).unwrap();
    
    loop {
        let now = Utc::now();
        if let Some(next) = schedule.upcoming(Utc).next() {
            let delay = next - now;
            let millis = delay.num_milliseconds().max(0) as u64;
            
            thread::sleep(Duration::from_millis(millis));
            
            println!("[{}] Задача по расписанию!", Local::now().format("%H:%M:%S"));
            // Выполнение задачи
        } else {
            thread::sleep(Duration::from_secs(1));
        }
    }
}

Промышленный планировщик с многопоточностью

Для серьёзных проектов создайте структуру Scheduler, которая управляет задачами в отдельных потоках:

use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};

type Job = Box<dyn Fn() + Send + Sync>;

struct ScheduledTask {
    name: String,
    interval: Duration,
    last_run: Instant,
    job: Arc<Job>,
}

pub struct Scheduler {
    tasks: Arc<Mutex<Vec<ScheduledTask>>>,
    running: Arc<std::sync::atomic::AtomicBool>,
}

impl Scheduler {
    pub fn new() -> Self {
        Scheduler {
            tasks: Arc::new(Mutex::new(Vec::new())),
            running: Arc::new(std::sync::atomic                    

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