8000 Exercise for shared mutability between threads · Issue #3 · rust-lang/rustlings · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

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

Closed
jdm opened this issue Sep 16, 2015 · 15 comments
Closed

Exercise for shared mutability between threads #3

jdm opened this issue Sep 16, 2015 · 15 comments

Comments

@jdm
Copy link
jdm commented Sep 16, 2015

Something like this, maybe:

// Make this compile!

use std::sync::Arc;
use std::thread;

struct JobStatus {
    jobs_completed: u32,
}

fn main() {
    let status = Arc::new(JobStatus { jobs_completed: 0 });
    let status_shared = status.clone();
    thread::spawn(move || {
        for _ in 0..10 {
            thread::sleep_ms(500);
            status_shared.jobs_completed += 1;
        }
    });
    while status.jobs_completed < 10 {
        println!("waiting...");
        thread::sleep_ms(1000);
    }
}
@carols10cents
Copy link
Member

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 :)

// Make this compile!

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(250);
            let mut status_shared = status_shared.lock().unwrap();
            status_shared.jobs_completed += 1;
        }
    });
    let status_check = status.clone();
    let mut jobs_completed = {
        let s = status_check.lock().unwrap();
        s.jobs_completed
    };
    while jobs_completed < 10 {
        println!("waiting... {}", jobs_completed);
        thread::sleep_ms(500);
        jobs_completed = {
            let s = status_check.lock().unwrap();
            s.jobs_completed
        };
    }
}

@jdm
Copy link
Author
jdm commented Sep 23, 2015

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);
    }
}

@carols10cents
Copy link
Member

I bumped the timeouts down because otherwise you hit the playground timeout limits :)

@sp-1234
Copy link
sp-1234 commented Aug 15, 2016

neither of the above did work for me — I was always deadlocking.
what did work is this:

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));
            status_shared.lock().unwrap().jobs_completed += 1;
        }
    });
    fn jobs_completed(s: &Arc<Mutex<JobStatus>>) -> u32 {
        s.lock().unwrap().jobs_completed
    };
    while jobs_completed(&status) < 10 {
        println!("waiting... ");
        thread::sleep(Duration::from_millis(500));
    }
}

So I had to put the main thread's lock into a function.

@Wieke
Copy link
Wieke commented Jan 3, 2017

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));
    }
}

@zphixon
Copy link
zphixon commented Mar 27, 2018

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));
}

@ehiggs
Copy link
ehiggs commented Mar 28, 2018

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(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!

@pickfire
Copy link
pickfire commented May 10, 2018

I got a solution similar to @jdm's solution except that status_shared was put into a variable.

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));
    }
}

@Gadzev
Copy link
Gadzev commented Apr 4, 2019

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));
    }
}

@superhawk610
Copy link
superhawk610 commented Apr 16, 2019

Any ideas why this only prints waiting... once?

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:

waiting... 
set completed jobs to 1
set completed jobs to 2
set completed jobs to 3
set completed jobs to 4
set completed jobs to 5
set completed jobs to 6
set completed jobs to 7
set completed jobs to 8
set completed jobs to 9
set completed jobs to 10

EDIT: Just figured it out! Moving the thread::sleep() to the end of the spawned thread will cause the spawned thread to always hold the lock on the mutex and never yield it back to the main thread, so the while loop will only run once!

@cyyyu
Copy link
cyyyu commented May 2, 2019

what I just learned from The Rust Programming Language is that you can actually make it even simpler:

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);
    let handle = thread::spawn(move || {
        for _ in 0..10 {
            thread::sleep(Duration::from_millis(250));
            let mut jobstatus = status_shared.lock().unwrap();
            jobstatus.jobs_completed += 1;
        }
    });

    handle.join().unwrap();

    println!("Jobs completed: {}", status.lock().unwrap().jobs_completed);
}

does my program make sense?

@jdm
Copy link
Author
jdm commented May 2, 2019

@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.

@cyyyu
Copy link
cyyyu commented May 2, 2019

@jdm I forgot the purpose 😅thanks for explanation.

@EinDev
Copy link
EinDev commented Apr 27, 2021

Is there any reason i shouldn't use AtomicU32 for this purpose?
According to this performance comparison i found it should also be faster than by using Mutex.
Of course you need to specify the byte ordering which may introduce confusion for beginners.
This is my (working) program:

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));
    }
}

@jdm
Copy link
Author
jdm commented Apr 27, 2021

AtomicU32 is a fine solution.

RomanLeman added a commit to RomanLeman/rustlings that referenced this issue Jan 31, 2022
ppp3 pushed a commit to ppp3/rustlings that referenced this issue May 23, 2022
TomaszWaszczyk referenced this issue in TomaszWaszczyk/rustlings Sep 2, 2022
fnandop added a commit to fnandop/rustlings that referenced this issue Aug 4, 2023
parx1050 added a commit to parx1050/rustlings that referenced this issue Nov 8, 2023
racampos added a commit to racampos/rustlings that referenced this issue Mar 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

0