-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Exercise for shared mutability between threads #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Ok, so, real talk, your exercises are not straightforward for me, I am a Ruby developer who has not spawned threads in many moons :) So before I write hints for this, I want to check that I got a solution along the lines of what you were thinking for this one: This was really great for me actually, because it put me more in the shoes of people who are newer to Rust than I am doing the exercises I wrote, which will help me write better hints, and I learned a lot working on this, which validates my hope that these are useful to people :)
|
Yep, that's equivalent to this minimal solution that I had in mind: use std::sync::{Arc, Mutex};
use std::thread;
struct JobStatus {
jobs_completed: u32,
}
fn main() {
let status = Arc::new(Mutex::new(JobStatus { jobs_completed: 0 }));
let status_shared = status.clone();
thread::spawn(move || {
for _ in 0..10 {
thread::sleep_ms(500);
status_shared.lock().unwrap().jobs_completed += 1;
}
});
while status.lock().unwrap().jobs_completed < 10 {
println!("waiting...");
thread::sleep_ms(1000);
}
} |
I bumped the timeouts down because otherwise you hit the playground timeout limits :) |
neither of the above did work for me — I was always deadlocking.
So I had to put the main thread's lock into a function. |
Anticipating a deadlock due to the while loop I opted to use a normal loop instead. The point being that I was unsure how the while loop would handle the lock, whether it would be released before the content of the loop was executed (though apparently it does so my approach was a bit more convoluted than necessary). (Also this way I could print the number of completed jobs.) struct JobStatus {
jobs_completed: u32,
}
fn main() {
let status = Arc::new(Mutex::new(JobStatus { jobs_completed: 0 }));
let status_shared = status.clone();
thread::spawn(move || {
for _ in 0..10 {
thread::sleep(Duration::from_millis(250));
let mut x = status_shared.lock().unwrap();
x.jobs_completed += 1;
}
});
loop {
{
let x = status.lock().unwrap();
println!("{} done", x.jobs_completed);
if x.jobs_completed >= 10 { break; }
} // Lock going out of scope
thread::sleep(Duration::from_millis(500));
}
} |
This syntax was called a weird hack but I think it's kind of cute here. while {
let handle = status.lock().unwrap();
println!("{} done", handle.jobs_completed);
handle.jobs_completed < 10
} {
println!("waiting... ");
thread::sleep(Duration::from_millis(500));
} |
The first hint gives a reference to a redirect page. In the 2nd version of the book, it starts talking about channels before shared state so I solved it using channels as: use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
for _ in 0..10 {
thread::sleep(Duration::from_millis(250));
tx.send(
8000
span>1).unwrap();
}
});
let mut received = 0;
while received < 10 {
received += rx.try_iter().sum::<i32>();
println!("waiting... ");
thread::sleep(Duration::from_millis(500));
}
} ... which is obviously not straight forward given the initial setup. But a nice little exercise! |
I got a solution similar to @jdm's solution except that use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
struct JobStatus {
jobs_completed: u32,
}
fn main() {
let status = Arc::new(Mutex::new(JobStatus { jobs_completed: 0 }));
let status_shared = status.clone();
thread::spawn(move || {
for _ in 0..10 {
thread::sleep(Duration::from_millis(250));
let mut status_shared = status_shared.lock().unwrap();
status_shared.jobs_completed += 1;
}
});
while status.lock().unwrap().jobs_completed < 10 {
println!("waiting... ");
thread::sleep(Duration::from_millis(500));
}
} |
This worked for me: use std::sync::{Mutex, Arc};
use std::thread;
use std::time::Duration;
struct JobStatus {
jobs_completed: Mutex<i32>,
}
fn main() {
let status = Arc::new(JobStatus { jobs_completed: Mutex::new(0) });
let status_shared = status.clone();
thread::spawn(move || {
for _ in 0..10 {
thread::sleep(Duration::from_millis(250));
let mut job_c = status_shared.jobs_completed.lock().unwrap();
*job_c += 1;
}
});
while *status.jobs_completed.lock().unwrap() < 10 {
println!("waiting... ");
thread::sleep(Duration::from_millis(500));
}
} |
Any ideas why this only prints use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
struct JobStatus {
jobs_completed: u32,
}
fn main() {
let status = Arc::new(Mutex::new(JobStatus { jobs_completed: 0 }));
let status_shared = Arc::clone(&status);
thread::spawn(move || {
for _ in 0..10 {
let mut stat = status_shared.lock().unwrap();
stat.jobs_completed += 1;
println!("set completed jobs to {}", stat.jobs_completed);
thread::sleep(Duration::from_millis(250));
}
});
while status.lock().unwrap().jobs_completed < 10 {
println!("waiting... ");
thread::sleep(Duration::from_millis(500));
}
} Output:
EDIT: Just figured it out! Moving the |
what I just learned from The Rust Programming Language is that you can actually make it even simpler:
does my program make sense? |
@cyyyu That is a valid program, but by using join() to block until the thread has finished running it avoids the main problem of the exercise, which is to experiment with sharing memory between two threads that are actively running simultaneously. |
@jdm I forgot the purpose 😅thanks for explanation. |
Is there any reason i shouldn't use AtomicU32 for this purpose? use std::sync::Arc;
use std::thread;
use std::time::Duration;
use std::sync::atomic::{AtomicU32, Ordering};
struct JobStatus {
jobs_completed: AtomicU32,
}
fn main() {
let status = Arc::new(JobStatus { jobs_completed: AtomicU32::new(0) });
let status_clone = Arc::clone(&status);
thread::spawn(move || {
for _ in 0..10 {
thread::sleep(Duration::from_millis(250));
status_clone.jobs_completed.fetch_add(1, Ordering::Relaxed);
}
});
while status.jobs_completed.load(Ordering::Relaxed) < 10 {
println!("waiting... ");
thread::sleep(Duration::from_millis(500));
}
} |
AtomicU32 is a fine solution. |
Closes rust-lang#3. Thank you @jdm!!! ❇️
Something like this, maybe:
The text was updated successfully, but these errors were encountered: