Как я могу запускать набор функций с повторяющимся интервалом, не запуская одну и ту же функцию одновременно, используя только стандартную библиотеку Rust?

Я хотел бы использовать Rust для создания простого планировщика, чтобы запускать несколько параллельных функций в определенное время, но не запускать больше, если они еще не завершены.

Например, если заданный интервал равен одной секунде, планировщик должен запускать функции и не запускать больше, если предыдущие функции не вернулись. Цель состоит в том, чтобы предотвратить многократное выполнение одной и той же функции.

Я создал рабочий пример с помощью Go следующим образом:

package main

import (
    "fmt"
    "sync"
    "time"
)

func myFunc(wg *sync.WaitGroup) {
    fmt.Printf("now: %+s\n", time.Now())
    time.Sleep(3 * time.Second)
    wg.Done()
}

func main() {
    quit := make(chan bool)

    t := time.NewTicker(time.Second)
    go func() {
        for {
            select {
            case <-t.C:
                var wg sync.WaitGroup
                for i := 0; i <= 4; i++ {
                    wg.Add(1)
                    go myFunc(&wg)
                }
                wg.Wait()
                fmt.Printf("--- done ---\n\n")
            case <-quit:
                return
            }
        }
    }()

    <-time.After(time.Minute)
    close(quit)
}

Поскольку в стандартной библиотеке Rust я не нашел ничего похожего на NewTicker Go, я использовал Токио и придумал это.

extern crate futures;
extern crate tokio;

use futures::future::lazy;
use std::{thread, time};
use tokio::prelude::*;
use tokio::timer::Interval;

fn main() {
    let task = Interval::new(time::Instant::now(), time::Duration::new(1, 0))
        .for_each(|interval| {
            println!("Interval: {:?}", interval);
            for i in 0..5 {
                tokio::spawn(lazy(move || {
                    println!("I am i: {}", i);
                    thread::sleep(time::Duration::from_secs(3));
                    Ok(())
                }));
            }
            Ok(())
        })
        .map_err(|e| panic!("interval errored; err={:?}", e));

    tokio::run(task);
}

Проблема, с которой я столкнулся при таком подходе, заключается в том, что задачи не ждут вызова предыдущих функций, поэтому функции запускаются снова, независимо от того, были ли они запущены ранее, здесь мне не хватает чего-то вроде Go sync.WaitGroup. Что можно использовать для достижения тех же результатов, что и в рабочем примере?

Можно ли добиться этого, используя только стандартную библиотеку? Это в основном для целей обучения, возможно, есть довольно простой способ сделать это, и я мог бы избежать дополнительной сложности.

В конце концов, я хотел бы периодически отслеживать некоторые сайты через HTTP (получать только возвращенный код состояния), но не запрашивать их все снова, пока не получу все ответы.

Использовать ли нетthread::sleep в будущем: Почему Future::select сначала выбирает будущее с более длительным периодом сна?

Shepmaster 22.05.2019 16:48

Если вы работаете каждую секунду, а функция занимает 10 секунд, должен ли быть перерыв в 1 секунду, прежде чем функция снова запустится, или она снова запустится немедленно?

Shepmaster 22.05.2019 16:59

@Shepmaster спасибо за все ваши комментарии, но из-за моего непонимания и опыта работы с RUST я пришел с тем, что вы описываете как непоследовательный вопрос, если это поможет в конце, я просто хотел бы знать, как RUST делает что-то как и в рабочем примере play.golang.org/p/ZLw6ESioqfu, моя точка зрения о том, как сделать это только с помощью std lib, связана с простотой этого с другими языками программирования и может считаться, вероятно, «лучшей практикой», но совершенно нормально использовать ящик, Я просто пытаюсь научиться делать все правильно :-)

nbari 22.05.2019 17:41
каков путь RUST — почему вы считаете, что один правильный путь? Вы, вероятно, можете сделать это с потоками, и вы, вероятно, можете сделать это с фьючерсами (и, возможно, другими способами). Ни один из них не является «правильным путем»; вот почему этот вопрос кажется слишком широким. Если вы можете сузить его до чего-то вроде «как я могу выполнить <эту задачу>, используя <этот аспект Rust>», то он кажется более актуальным для Stack Overflow. (И это просто "Rust", не все заглавные, не все строчные).
Shepmaster 22.05.2019 18:05

@Shepmaster понял, спасибо за внимание, я был бы признателен за конструктивный ответ, который мог бы помочь мне достичь того, что описано в вопросе, например.

nbari 22.05.2019 18:52
3 метода стилизации элементов HTML
3 метода стилизации элементов HTML
Когда дело доходит до применения какого-либо стиля к нашему HTML, существует три подхода: встроенный, внутренний и внешний. Предпочтительным обычно...
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
3
5
6 183
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Поскольку вам нужен параллелизм и вы будете использовать только стандартную библиотеку, вы в основном должны использовать потоки.

Здесь мы запускаем поток для каждой функции для каждой итерации цикла планировщика, позволяя им работать параллельно. Затем мы ждем завершения всех функций, предотвращая одновременный запуск одной и той же функции дважды.

use std::{
    thread,
    time::{Duration, Instant},
};

fn main() {
    let scheduler = thread::spawn(|| {
        let wait_time = Duration::from_millis(500);

        // Make this an infinite loop
        // Or some control path to exit the loop
        for _ in 0..5 {
            let start = Instant::now();
            eprintln!("Scheduler starting at {:?}", start);

            let thread_a = thread::spawn(a);
            let thread_b = thread::spawn(b);

            thread_a.join().expect("Thread A panicked");
            thread_b.join().expect("Thread B panicked");

            let runtime = start.elapsed();

            if let Some(remaining) = wait_time.checked_sub(runtime) {
                eprintln!(
                    "schedule slice has time left over; sleeping for {:?}",
                    remaining
                );
                thread::sleep(remaining);
            }
        }
    });

    scheduler.join().expect("Scheduler panicked");
}

fn a() {
    eprintln!("a");
    thread::sleep(Duration::from_millis(100))
}
fn b() {
    eprintln!("b");
    thread::sleep(Duration::from_millis(200))
}

Вы также можете использовать Barrier для запуска каждой функции в потоке один раз, а затем синхронизировать их все в конце выполнения:

use std::{
    sync::{Arc, Barrier},
    thread,
    time::Duration,
};

fn main() {
    let scheduler = thread::spawn(|| {
        let barrier = Arc::new(Barrier::new(2));

        fn with_barrier(barrier: Arc<Barrier>, f: impl Fn()) -> impl Fn() {
            move || {
                // Make this an infinite loop
                // Or some control path to exit the loop
                for _ in 0..5 {
                    f();
                    barrier.wait();
                }
            }
        }

        let thread_a = thread::spawn(with_barrier(barrier.clone(), a));
        let thread_b = thread::spawn(with_barrier(barrier.clone(), b));

        thread_a.join().expect("Thread A panicked");
        thread_b.join().expect("Thread B panicked");
    });

    scheduler.join().expect("Scheduler panicked");
}

fn a() {
    eprintln!("a");
    thread::sleep(Duration::from_millis(100))
}
fn b() {
    eprintln!("b");
    thread::sleep(Duration::from_millis(200))
}

Лично я бы не стал использовать либо этих решений. Я бы поставил найти ящик, если кто-то другой написал и протестировал нужный мне код.

Смотрите также:

очень признателен, я опубликую еще один вопрос об использовании Токио, следуя вашему совету.

nbari 23.05.2019 11:59

Другие вопросы по теме