From c60070bb80be90b2338389a5e5045b7878039977 Mon Sep 17 00:00:00 2001 From: Frances Wingerter <91758128+fw-immunant@users.noreply.github.com> Date: Fri, 23 May 2025 19:54:14 +0000 Subject: [PATCH] Small fixes to concurrency material (#2737) See individual commits. --- src/concurrency/shared-state/arc.md | 23 +++++++++-- src/concurrency/shared-state/example.md | 38 +++++++++---------- .../sync-exercises/link-checker.rs | 3 +- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/concurrency/shared-state/arc.md b/src/concurrency/shared-state/arc.md index 8bc5ff17..6fa07500 100644 --- a/src/concurrency/shared-state/arc.md +++ b/src/concurrency/shared-state/arc.md @@ -4,25 +4,40 @@ minutes: 5 # `Arc` -[`Arc`][1] allows shared read-only access via `Arc::clone`: +[`Arc`][1] allows shared, read-only ownership via `Arc::clone`: ```rust,editable use std::sync::Arc; use std::thread; +/// A struct that prints which thread drops it. +#[derive(Debug)] +struct WhereDropped(Vec); + +impl Drop for WhereDropped { + fn drop(&mut self) { + println!("Dropped by {:?}", thread::current().id()) + } +} + fn main() { - let v = Arc::new(vec![10, 20, 30]); + let v = Arc::new(WhereDropped(vec![10, 20, 30])); let mut handles = Vec::new(); - for _ in 0..5 { + for i in 0..5 { let v = Arc::clone(&v); handles.push(thread::spawn(move || { + // Sleep for 0-500ms. + std::thread::sleep(std::time::Duration::from_millis(500 - i * 100)); let thread_id = thread::current().id(); println!("{thread_id:?}: {v:?}"); })); } + // Now only the spawned threads will hold clones of `v`. + drop(v); + + // When the last spawned thread finishes, it will drop `v`'s contents. handles.into_iter().for_each(|h| h.join().unwrap()); - println!("v: {v:?}"); } ``` diff --git a/src/concurrency/shared-state/example.md b/src/concurrency/shared-state/example.md index 180d9f94..419ea189 100644 --- a/src/concurrency/shared-state/example.md +++ b/src/concurrency/shared-state/example.md @@ -12,13 +12,15 @@ use std::thread; fn main() { let v = vec![10, 20, 30]; - let handle = thread::spawn(|| { - v.push(10); - }); - v.push(1000); + let mut handles = Vec::new(); + for i in 0..5 { + handles.push(thread::spawn(|| { + v.push(10 * i); + println!("v: {v:?}"); + })); + } - handle.join().unwrap(); - println!("v: {v:?}"); + handles.into_iter().for_each(|h| h.join().unwrap()); } ``` @@ -32,21 +34,17 @@ use std::thread; fn main() { let v = Arc::new(Mutex::new(vec![10, 20, 30])); - - let v2 = Arc::clone(&v); - let handle = thread::spawn(move || { - let mut v2 = v2.lock().unwrap(); - v2.push(10); - }); - - { - let mut v = v.lock().unwrap(); - v.push(1000); + let mut handles = Vec::new(); + for i in 0..5 { + let v = Arc::clone(&v); + handles.push(thread::spawn(move || { + let mut v = v.lock().unwrap(); + v.push(10 * i); + println!("v: {v:?}"); + })); } - handle.join().unwrap(); - - println!("v: {v:?}"); + handles.into_iter().for_each(|h| h.join().unwrap()); } ``` @@ -56,7 +54,7 @@ Notable parts: orthogonal. - Wrapping a `Mutex` in an `Arc` is a common pattern to share mutable state between threads. -- `v: Arc<_>` needs to be cloned as `v2` before it can be moved into another +- `v: Arc<_>` needs to be cloned to make a new reference for each new spawned thread. Note `move` was added to the lambda signature. - Blocks are introduced to narrow the scope of the `LockGuard` as much as possible. diff --git a/src/concurrency/sync-exercises/link-checker.rs b/src/concurrency/sync-exercises/link-checker.rs index eee665cb..4063851e 100644 --- a/src/concurrency/sync-exercises/link-checker.rs +++ b/src/concurrency/sync-exercises/link-checker.rs @@ -105,11 +105,12 @@ fn spawn_crawler_threads( result_sender: mpsc::Sender, thread_count: u32, ) { + // To multiplex the non-cloneable Receiver, wrap it in Arc>. let command_receiver = Arc::new(Mutex::new(command_receiver)); for _ in 0..thread_count { let result_sender = result_sender.clone(); - let command_receiver = command_receiver.clone(); + let command_receiver = Arc::clone(&command_receiver); thread::spawn(move || { let client = Client::new(); loop {