问题
I would like to use Rust to create a simple scheduler in order to run multiple concurrent functions at a defined time but do not start more if they haven't finished.
For example, if the defined interval is one second, the scheduler should run the functions and don't start more if the previous functions have not returned. The goal is to prevent running the same function multiple times.
I created a working example with Go like this:
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)
}
Since I didn't find something like Go's NewTicker
within the Rust standard library, I used Tokio and came up with this
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);
}
The problem I have with this approach is that the tasks don't wait for the previous functions to be called therefore the functions start again no matter if previously they were running, I am missing here something like Go's sync.WaitGroup
. What could be used to achieve the same results as in the working example?
Is it possible to achieve this by only using the standard library? This is mainly for learning purposes, probably there is a pretty straightforward way of doing it and I could avoid extra complexity.
In the end, I would like to periodically monitor some sites via HTTP (get just the returned status code) but don't query all of them again until I have all the responses.
回答1:
Since you want concurrency and will only use the standard library, then you basically must use threads.
Here, we start a thread for every function for every iteration of the scheduler loop, allowing them to run in parallel. We then wait for all functions to finish, preventing ever running the same function twice concurrently.
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))
}
You could also make use of a Barrier to start each function in a thread once and then synchronize all of them at the end of execution:
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))
}
I wouldn't use either of these solutions, personally. I'd find a crate where someone else has written and tested the code I need.
See also:
- Does Rust have an equivalent of Python's threading.Timer?
- Is there a way to schedule a task at a specific time or with an interval?
- How do I emulate a timer inside an object that will periodically mutate the object?
来源:https://stackoverflow.com/questions/56253623/how-can-i-run-a-set-of-functions-on-a-recurring-interval-without-running-the-sam