Condition variable not playing well with thread::sleep - multithreading

I'm not sure I understand Rust's concurrency support with Mutexes and condition variables. In the following code, the main thread sets the poll_thread to be idle for two seconds, then to "read a register" for 2 seconds, and then return to "idle":
use std::thread;
use std::sync::{Arc, Mutex, Condvar};
use std::time;
#[derive(PartialEq, Debug)]
enum Command {
Idle,
ReadRegister(u32),
}
fn poll_thread(sync_pair: Arc<(Mutex<Command>, Condvar)>) {
let &(ref mutex, ref cvar) = &*sync_pair;
loop {
let mut flag = mutex.lock().unwrap();
while *flag == Command::Idle {
flag = cvar.wait(flag).unwrap();
}
match *flag {
Command::Idle => {
println!("WHAT IMPOSSIBLE!");
panic!();
}
Command::ReadRegister(i) => {
println!("You want me to read {}?", i);
thread::sleep(time::Duration::from_millis(450));
println!("Ok, here it is: {}", 42);
}
}
}
}
pub fn main() {
let pair = Arc::new((Mutex::new(Command::Idle), Condvar::new()));
let pclone = pair.clone();
let rx_thread = thread::spawn(|| poll_thread(pclone));
let &(ref mutex, ref cvar) = &*pair;
for i in 0..10 {
thread::sleep(time::Duration::from_millis(500));
if i == 4 {
println!("Setting ReadRegister");
let mut flag = mutex.lock().unwrap();
*flag = Command::ReadRegister(5);
println!("flag is = {:?}", *flag);
cvar.notify_one();
} else if i == 8 {
println!("Setting Idle");
let mut flag = mutex.lock().unwrap();
*flag = Command::Idle;
println!("flag is = {:?}", *flag);
cvar.notify_one();
}
}
println!("after notify_one()");
rx_thread.join();
}
This works as expected, but when the line to sleep for 450 milliseconds is uncommented, the code will often remain in the "read" state and not return to waiting on the condition variable cvar.wait(). Sometimes it will return to idle after, say, 15 seconds!
I would think that when poll_thread reaches the bottom of the loop, it would release the lock, allowing main to acquire and set flag = Command::Idle, and within roughly half a second, poll_thread would return to idle, but it appears that isn't happening when poll_thread sleeps. Why?

Related

egui interaction with background thread

My goal is to have a background thread counting up every second, which can be started and stopped by buttons on the UI. The gui should display the current time. I thought that the communication between the gui and the background thread could be done via channels for the commands, and via a mutex for the number being counted upwards. In this example, I have omitted the communication via channels, and just focus on the mutex.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(320.0, 240.0)),
fullscreen: false,
default_theme: eframe::Theme::Light,
..Default::default()
};
eframe::run_native(
"My egui App",
options,
Box::new(|_cc| Box::new(Timer::default())),
);
}
struct Timer {
time: u32,
counter: Arc<Mutex<i32>>,
}
impl Default for Timer {
fn default() -> Self {
let counter = Arc::new(Mutex::new(0));
let counter_clone = Arc::clone(&counter);
//spawn looping thread
let _ = thread::spawn(move || {
loop {
let mut num = counter_clone.lock().unwrap();
*num += 1;
std::thread::sleep_ms(100);
}
});
Self {
time: 0,
counter: counter,
}
}
}
impl eframe::App for Timer {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
let mut num = self.counter.lock().unwrap();
ui.heading(format!("My egui Application {}", num));
});
}
}
This compiles and runs, but it is super laggy. Could you help me to understand what I can do better?
In your sleeping loop you hold the lock for the whole duration you're sleeping.
loop {
let mut num = counter_clone.lock().unwrap();
*num += 1;
std::thread::sleep_ms(100);
} // counter_clone only gets released here.
You either want to add a manual call to drop after you increase the number:
loop {
let mut num = counter_clone.lock().unwrap();
*num += 1;
drop(num); // counter_clone now gets released here before we wait.
std::thread::sleep_ms(100);
}
Or just lock it in place:
loop {
*counter_clone().lock.unwrap() += 1; // counter_clone gets unlocked at the end of this statement.
std::thread::sleep_ms(100);
}

Sharing state between threads with notify-rs

i'm new to rust.
I'm trying to write a file_sensor that will start a counter after a file is created. The plan is that after an amount of time, if a second file is not received the sensor will exit with a zero exit code.
I could write the code to continue that work but i feel the code below illustrates the problem (i have also missed for example the post function referred to)
I have been struggling with this problem for several hours, i've tried Arc and mutex's and even global variables.
The Timer implementation is Ticktock-rs
I need to be able to either get heartbeat in the match body for EventKind::Create(CreateKind::Folder) or file_count in the loop
The code i've attached here runs but file_count is always zero in the loop.
use std::env;
use std::path::Path;
use std::{thread, time};
use std::process::ExitCode;
use ticktock::Timer;
use notify::{
Watcher,
RecommendedWatcher,
RecursiveMode,
Result,
event::{EventKind, CreateKind, ModifyKind, Event}
};
fn main() -> Result<()> {
let now = time::Instant::now();
let mut heartbeat = Timer::apply(
|_, count| {
*count += 1;
*count
},
0,
)
.every(time::Duration::from_millis(500))
.start(now);
let mut file_count = 0;
let args = Args::parse();
let REQUEST_SENSOR_PATH = env::var("REQUEST_SENSOR_PATH").expect("$REQUEST_SENSOR_PATH} is not set");
let mut watcher = notify::recommended_watcher(move|res: Result<Event>| {
match res {
Ok(event) => {
match event.kind {
EventKind::Create(CreateKind::File) => {
file_count += 1;
// do something with file
}
_ => { /* something else changed */ }
}
println!("{:?}", event);
},
Err(e) => {
println!("watch error: {:?}", e);
ExitCode::from(101);
},
}
})?;
watcher.watch(Path::new(&REQUEST_SENSOR_PATH), RecursiveMode::Recursive)?;
loop {
let now = time::Instant::now();
if let Some(n) = heartbeat.update(now){
println!("Heartbeat: {}, fileCount: {}", n, file_count);
if n > 10 {
heartbeat.set_value(0);
// This function will reset timer when a file arrives
}
}
}
Ok(())
}
Your compiler warnings show you the problem:
warning: unused variable: `file_count`
--> src/main.rs:31:25
|
31 | file_count += 1;
| ^^^^^^^^^^
|
= note: `#[warn(unused_variables)]` on by default
= help: did you mean to capture by reference instead?
The problem here is that you use file_count inside of a move || closure. file_count is an i32, which is Copy. Using it in a move || closure actually creates a copy of it, which does no longer update the original variable if you assign to it.
Either way, it's impossible to modify a variable in main() from an event handler. Event handlers require 'static lifetime if they reference things, because Rust cannot guarantee that the event handler lives shorter than main.
One solution for this problem is to use reference counters and interior mutability. In this case, I will use Arc for reference counters and AtomicI32 for interior mutability. Note that notify::recommended_watcher requires thread safety, otherwise instead of an Arc<AtomicI32> we could have used an Rc<Cell<i32>>, which is the same thing but only for single-threaded environments, with a little less overhead.
use notify::{
event::{CreateKind, Event, EventKind},
RecursiveMode, Result, Watcher,
};
use std::time;
use std::{env, sync::atomic::Ordering};
use std::{path::Path, sync::Arc};
use std::{process::ExitCode, sync::atomic::AtomicI32};
use ticktock::Timer;
fn main() -> Result<()> {
let now = time::Instant::now();
let mut heartbeat = Timer::apply(
|_, count| {
*count += 1;
*count
},
0,
)
.every(time::Duration::from_millis(500))
.start(now);
let file_count = Arc::new(AtomicI32::new(0));
let REQUEST_SENSOR_PATH =
env::var("REQUEST_SENSOR_PATH").expect("$REQUEST_SENSOR_PATH} is not set");
let mut watcher = notify::recommended_watcher({
let file_count = Arc::clone(&file_count);
move |res: Result<Event>| {
match res {
Ok(event) => {
match event.kind {
EventKind::Create(CreateKind::File) => {
file_count.fetch_add(1, Ordering::AcqRel);
// do something with file
}
_ => { /* something else changed */ }
}
println!("{:?}", event);
}
Err(e) => {
println!("watch error: {:?}", e);
ExitCode::from(101);
}
}
}
})?;
watcher.watch(Path::new(&REQUEST_SENSOR_PATH), RecursiveMode::Recursive)?;
loop {
let now = time::Instant::now();
if let Some(n) = heartbeat.update(now) {
println!(
"Heartbeat: {}, fileCount: {}",
n,
file_count.load(Ordering::Acquire)
);
if n > 10 {
heartbeat.set_value(0);
// This function will reset timer when a file arrives
}
}
}
}
Also, note that the ExitCode::from(101); gives you a warning. It does not actually exit the program, it only creates an exit code variable and then discards it again. You probably intended to write std::process::exit(101);. Although I would discourage it, because it does not properly clean up (does not call any Drop implementations). I'd use panic here, instead. This is the exact usecase panic is meant for.

How to create a critical section with Mutex in Rust?

I'm new to Rust. I'm supposed to use a Mutex and an Arc to create a critical section within the print_lots function to stop the race condition from happening. Any Ideas?
fn main() {
let num_of_threads = 4;
let mut array_of_threads = vec![];
for id in 0..num_of_threads {
array_of_threads.push(std::thread::spawn(move || print_lots(id)));
}
for t in array_of_threads {
t.join().expect("Thread join failure");
}
}
fn print_lots(id: u32) {
println!("Begin [{}]", id);
for _i in 0..100 {
print!("{} ", id);
}
println!("\nEnd [{}]", id);
}
Mutex in Rust perhaps works differently to how locks work in some other languages you might be used to. Instead of tracking the lock independently from the value, a Rust Mutex owns the data and prevents accessing it without first obtaining a lock, which is enforced at compile time.
The warning you are getting is because you have locked the Mutex, but then done nothing with the value. The warning is there because this is almost certainly a mistake.
fn main() {
let foo = Mutex::new(0);
// It's often best to just unwrap and panic if the lock is poisoned
if let Ok(mut lock) = foo.lock() {
*lock = 2;
// The mutex is unlocked automatically when lock goes out of scope here
}
println!("{:?}", foo); // Mutex { data: 2 }
}
I am guessing that your real problem is that you want to synchronise the print statements so that output from different threads is not intermingled.
One way to do that is to obtain a lock on StdOut which actually uses a lock internally and provides a similar API to Mutex:
fn print_lots(id: u32) {
let stdout = io::stdout();
println!("Begin [{}]", id);
let mut handle = stdout.lock();
for _i in 0..100 {
write!(&mut handle, "{} ", id).unwrap();
}
println!("\nEnd [{}]", id);
// handle is dropped here, unlocking stdout
}
In your simplified example, creating a long-lived lock in each thread is counterproductive since each thread will block the others and the result is sequential rather than concurrent. This might still make sense though if your real-world code has more going on.
use std::sync::{Arc, Mutex};
fn main() {
let num_of_threads = 4;
let mut array_of_threads = vec![];
let counter = Arc::new(Mutex::new(0));
for id in 0..num_of_threads {
let counter_clone = counter.clone();
array_of_threads.push(std::thread::spawn(move || print_lots(id, counter_clone)));
}
for t in array_of_threads {
t.join().expect("Thread join failure");
}
}
fn print_lots(id: u32, c: Arc<Mutex<u32>>) {
println!("Begin [{}]", id);
let _guard = c.lock().unwrap();
for _i in 0..100 {
print!("{} ", id);
}
println!("\nEnd [{}]", id);
}

Terminal state not restored using termion

I am trying to get the user input after a certain duration by using two threads. A thread duration and thread for editing. When the thread duration completes,and that the thread for editing has not completed,the terminal state is not restored thus breaking the terminal. This happens when the user did not press "q" before the time duration
The only way of restoring the state of the terminal is to press"q" which will break the loop in the first thread calling droop on the termion raw terminal
use std::io;
use std::io::Write;
use crossbeam_channel::{select, unbounded};
use std::thread;
use std::time;
use std::time::Duration;
use termion;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
fn test() -> String {
let (s1, r1) = unbounded();
let (s2, r2) = unbounded();
let terminal = io::stdout().into_raw_mode();
let mut stdout = terminal.unwrap();
let mut stdin = termion::async_stdin().keys();
thread::spawn(move || {
// Use asynchronous stdin
let mut s = String::new();
loop {
// Read input (if any)
let input = stdin.next();
// If a key was pressed
if let Some(Ok(key)) = input {
match key {
// Exit if 'q' is pressed
termion::event::Key::Char('q') => {
s1.send('q');
break;
}
// Else print the pressed key
_ => {
if let termion::event::Key::Char(k) = key {
s1.send(k);
}
stdout.lock().flush().unwrap();
}
}
}
thread::sleep(time::Duration::from_millis(50));
}
});
thread::spawn(move || {
thread::sleep(Duration::from_millis(3000));
s2.send(20).unwrap();
});
// None of the two operations will become ready within 100 milliseconds.
let mut val: String = String::new();
loop {
select! {
recv(r1) -> msg => val.push(msg.unwrap()),
recv(r2) -> _msg => break,
default(Duration::from_millis(3000)) => println!("timed out"),
};
}
return val;
}
fn main() {
println!("result {}", test());
}
In Rust, forcefully exiting a thread (such as by ending the main thread before the child threads run) is almost never a good idea, for reasons you've seen here. Their destructors don't get run, which means things could get messed up. The cleanest way is probably to keep an Arc<Mutex<bool>> that becomes true when threads should exit, and the threads can read it on their own accord and exit gracefully. Then, you should join the threads at the end of the function to ensure they finish all the way through. I've documented my changes in the comments:
use std::io;
use std::io::Write;
use crossbeam_channel::{select, unbounded};
use std::thread;
use std::time;
use std::time::Duration;
// import Arc and Mutex
use std::sync::{Arc, Mutex};
use termion;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
fn test() -> String {
let (s1, r1) = unbounded();
let (s2, r2) = unbounded();
let terminal = io::stdout().into_raw_mode();
let stdout = terminal.unwrap();
let mut stdin = termion::async_stdin().keys();
// keep a boolean flag of if we should exit
let should_exit = Arc::new(Mutex::new(false));
// clone the Arc for moving into the first thread
let should_exit_t1 = Arc::clone(&should_exit);
// keep a vec of handles for joining
let mut handles = vec![];
// push the handle onto the vec
handles.push(thread::spawn(move || {
loop {
// if the flag is true then we should gracefully exit
if *should_exit_t1.lock().unwrap() {
break;
}
// Read input (if any)
let input = stdin.next();
// If a key was pressed
if let Some(Ok(key)) = input {
match key {
// Exit if 'q' is pressed
termion::event::Key::Char('q') => {
s1.send('q').unwrap();
break;
}
// Else print the pressed key
_ => {
if let termion::event::Key::Char(k) = key {
s1.send(k).unwrap();
}
stdout.lock().flush().unwrap();
}
}
}
thread::sleep(time::Duration::from_millis(50));
}
}));
// also push the handle onto the vec
handles.push(thread::spawn(move || {
thread::sleep(Duration::from_millis(3000));
s2.send(20).unwrap();
}));
// None of the two operations will become ready within 100 milliseconds.
let mut val: String = String::new();
loop {
select! {
recv(r1) -> msg => val.push(msg.unwrap()),
recv(r2) -> _msg => break,
default(Duration::from_millis(3000)) => println!("timed out"),
};
}
// before exiting, set the exit flag to true
*should_exit.lock().unwrap() = true;
// join all the threads so their destructors are run
for handle in handles {
handle.join().unwrap();
}
return val;
}
fn main() {
println!("result {}", test());
}

Skip part of loop while thread is sleeping

I have two parts of code that I want to run in a loop. Sometimes I need to make the loop 'sleep', making each iteration skip the second part. The loop should stop sleeping after a set amount of time (for example using a thread with a call to thread::sleep). How do I accomplish this?
use std::thread;
let mut sleeping = false;
let mut handle = thread::spawn(|| {});
loop {
part_1();
if sleeping {
continue;
}
part_2();
if some_condition {
sleeping = true;
handle = thread::spawn(|| thread::sleep_ms(100));
}
}
In this example, if the condition is met, the part_2 call would be skipped for some amount of iterations. My use case is continuing to run graphical updates in a game, while freezing the game's logic (such as counting down timers).
There is no need for the overhead of threads or even the need to sleep. Simply track the time that you should delay executing code until:
use std::time::{Duration, Instant};
fn part_1() {}
fn part_2() {}
fn some_condition() -> bool {
false
}
fn main() {
let mut sleep_until = None;
loop {
part_1();
if let Some(until) = sleep_until {
if until > Instant::now() {
continue;
}
}
part_2();
if some_condition() {
let now = Instant::now();
let until = now + Duration::from_millis(500);
sleep_until = Some(until);
}
}
}
Although I'd probably avoid the use of continue here, and instead embed the logic within:
use std::time::{Duration, Instant};
fn perform_physics_calculation() {}
fn perform_graphics_render() {}
fn main() {
let mut next_graphics_update = Instant::now();
let graphics_delay = Duration::from_millis(500);
loop {
let now = Instant::now();
perform_physics_calculation();
if next_graphics_update <= now {
perform_graphics_render();
next_graphics_update = now + graphics_delay;
}
}
}
Note in one case I use an Option<Instant> and in the other I just use an Instant; both cases can make sense.
Turn your sleeping variable into a reference-counted atomic boolean so that you can reset it on the sleeping thread.
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
fn part_1() {}
fn part_2() {}
fn some_condition() -> bool { false }
fn main() {
let sleeping = Arc::new(AtomicBool::new(false));
let mut handle = None;
loop {
part_1();
if sleeping.load(Ordering::Acquire) {
continue;
}
part_2();
if some_condition() {
sleeping.store(true, Ordering::Release);
let sleeping_clone = sleeping.clone();
handle = Some(thread::spawn(move || {
thread::sleep(Duration::from_millis(100));
sleeping_clone.store(false, Ordering::Release);
}));
}
}
}

Resources