i'm trying to find a way to share a value between threads but the value updates automatically, for example:
// inside of my_value.do_something
self.idx += 1;
std::thread::sleep(std::time::Duration::from_milis(1000);
self.idx += 1;
// run my_value.do_something on thread
my_value.do_something() // this will run on another thread
// on the main thread
loop {
println!("{}", my_value.x); // normally with a
std::thread::sleep(std::time::Duration::from_milis(200);
}
this code is just a example/concept. If there is any way i can share a value between threads without the limitations of a Mutex would be nice!
If you only need to share numbers, you could use atomics. Otherwise, you need something more complex like a channel or mutex.
Which particular limitations are you trying to avoid?
the limitation i'm facing is that the mutex guard (in the thread) needs to go out of scope for the value (in the main thread) to update. It's a big problem in my case. I want either a function that updates the value (without dropping it) or just do it fully automatically
NOTE: The value being contained and shared with the threads is a Box aka boxed trait
Are you aware you can call drop(guard)
to drop the guard early, before the end of the current scope? If so, why is doing this a big problem? Is there some difficulty in acquiring the lock a second time?
If you can't use atomics, you'll have to acquire and drop a lock of some kind. (Or send a message over a channel.)
yes i'm aware, The Element trait has a function: fn event(&mut self, _event: event::Event) this function runs on a keypress, etc. and i the thread IS running this function, this function updates the data of the element, in the main thread it just renders.
my concern about drop is that how do i acquire the element back? especially in a situation like this
Please show the code, I don't know what you mean.
Use an Arc around the Mutex (or RwLock), and clone the Arc once for each place it is shared. This is the typical way of sharing a mutable value among threads.
https://doc.rust-lang.org/book/ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads
1 Like
pub fn run(elem: Box<dyn Element>) {
std::thread::spawn(move || loop {
if let Some(key) = read_ckey() {
elem.event(crate::event::Event::Key(key)); // need mutable access to elem
}
});
loop {
let (width, height) = get_term_size();
let mut frame: Vec<String> = create_frame(Value::Custom(width), Value::Custom(height));
render_to_frame(0, width, &mut frame, /* &Box<dyn Element> */); // ref of elem needed here
clear();
print!("{}", frame.join(""));
flush();
}
}
The should be:
pub fn run(elem: Arc<Mutex<dyn Element>>) {
to share elem
among threads.
If you can't change the param type, you can wrap it in Arc::new(Mutex::new(...))
.
See the doc I linked to learn about cloning the Arc. In your case you just need to clone it once.
So, something like this:
pub fn run(elem: Box<dyn Element>) {
let elem = Arc::new(Mutex::new(elem)); // <<<<<<<<<<<<<
std::thread::spawn({
let elem = elem.clone(); // <<<<<<<<<<<<<
move || loop {
if let Some(key) = read_ckey() {
elem.event(crate::event::Event::Key(key)); // need mutable access to elem
}
}
});
Of course, you will have to lock the Mutex each time you use the enclosed elem
.
i have done research about arc mutexes and i've used them, again i will mention that it's not usable for my case, at that point i might aswell use single threading, Thank you for your response but i can't use mutexes because they need to be dropped to update the value being passed to the main thread. which literally defeats the purpose
i don't wan't to waste your time.
The Arc owns the Mutex, which owns the elem
. You can obtain a lock guard from the Mutex. When the guard is dropped, the lock is released. But the Mutex is not dropped, it is still available in the Arc, in both threads.
I don't see a problem doing this in the code you posted. If you can show what problem you have when using an Arc<Mutex<...>>
as shown, I'm happy to help further.
Oh, or are you saying you can't allow blocking access to the elem
in one thread while the other thread is accessing it? I'm guessing because you said:
i might as well use single threading
Okay in more technical terms:
event, the function the thread calls (also the function in the Element trait), constantly updates whatever is inside the struct implementing the Element trait,
to add to that Element also has another function called render which takes &self and returns String. render is used on the main loop, it's constantly being called.
now if you use a Mutex i don't know how i would update the whole thing so that the main loop (render) can adjust to the new value,
With a Arc and Mutex i'm basically not changing the value for render until
if let Some(key) = read_ckey() {
elem.lock().unwrap().event(crate::event::Event::Key(key));
}
goes out of scope
here is some code you can run to see what i mean
use std::sync::{Arc, Mutex};
use std::thread::{sleep, spawn};
use std::time::Duration;
// equivalent to dyn Element.event(&mut self, event)
fn some_event(this: &mut i32) {
for _ in 0..10 {
*this += 1;
sleep(Duration::from_millis(500));
// simulate work
}
}
fn main() {
// This program will create a thread that increases
let my_val = Arc::new(Mutex::new(0));
let my_val_thread = Arc::clone(&my_val);
spawn(move || { // currently no loop but act like it's reading a key with a loop
let mut my_val = my_val_thread.lock().unwrap();
some_event(&mut my_val);
});
loop {
println!("{}", my_val.lock().unwrap());
sleep(Duration::from_millis(100));
}
}
it basically waits until the program is done (Keep in mind this is a simplified example)
okay i'm making a fix, i'll update you guys if it works
When the loop is added to the thread, your main thread is starved because the other thread immediately acquires the lock after releasing it.
A fair mutex might avoid that (as would yielding at the the top of the loop), but if you must continuously update the item, then a mutex is probably not what you want, as you said.
One approach is to have two items, one being updated while the other is being rendered. When updating is complete, the new item is copied to the old, and the variables are swapped. This may be necessary if updating continuously and updating requires exclusive access.
Another option is to update less frequently to give the render a chance to run. And perhaps do smaller updates.
Are you not using a gui/game library that supports this sort of thing?
i'm making my own TUI library.
It may be educational to see what ratatui is doing.
2 Likes
so i tried to make a callback to where i sent &mut self (on event) to update then drop the value, like this:
let elem = std::sync::Arc::new(std::sync::Mutex::new(elem));
let elem_thread = std::sync::Arc::clone(&elem);
let elem_thread_pass = std::sync::Arc::clone(&elem);
std::thread::spawn(move || loop {
if let Some(key) = read_ckey() {
let mut e = elem_thread.lock().unwrap().clone();
e.event(
&move |e: Box<dyn Element>| {
let mut e1 = elem_thread_pass.lock().unwrap();
*e1 = e;
},
crate::event::Event::Key(key),
);
}
});
Sadly because of rust, i also cannot do this. I wish rust wasn't so strict
Does anyone have any ideas to implement a non-blocking Mutex? or a alternative, I'm also going to ask ChatGPT
One approach is to use a channel, and have the update thread send the new element to the rendering thread whenever it is updated.
use std::sync::mpsc::{self, TryRecvError};
use std::thread;
use std::time::Duration;
#[derive(Clone)]
struct Element(i32);
impl Element {
fn update(&mut self) {
self.0 += 1;
// do some actually useful work here
thread::sleep(Duration::from_millis(500));
}
fn render(&self) {
// render the element
println!("Element: {}", self.0);
// don't completely spam the example
thread::sleep(Duration::from_millis(100));
}
}
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let mut element = Element(0);
loop {
element.update();
tx.send(element.clone())
.expect("main thread has closed the reciever (should be unreachable)");
}
});
// Initialize with the first element sent over
let mut current_element = rx.recv().expect("updater thread crashed");
loop {
// Attempt to recieve a value from the update thread
match rx.try_recv() {
Ok(new) => current_element = new,
// Maybe actually handle the update thread crashing
Err(TryRecvError::Disconnected) => panic!("updater thread closed"),
// updater thread didn't send a new element this iteration,
// no big deal. just use the existing one
Err(TryRecvError::Empty) => (),
}
current_element.render();
}
}
This concept of updating and rendering on two separate values is similar to @ jumpnbrownweasel’s suggestion of having two items and swapping which one is being rendered/updated. That will likely be more performant, but you would have to convince the borrow checker that your code is correct.
1 Like
i'm making more of a react/dioxus like TUI library that's easy to make components and use components, imo ratatui isn't my kind of TUI library