diff --git a/exercise/threads_channels/src/main.rs b/exercise/threads_channels/src/main.rs index 73f6e44..fac7c3e 100644 --- a/exercise/threads_channels/src/main.rs +++ b/exercise/threads_channels/src/main.rs @@ -22,8 +22,8 @@ fn main() { // join handle in a variable called `handle`. Once you've done this you should be able to run // the code and see the output from the child thread's expensive sum in the middle of the main // thread's processing of letters. - // - //let handle = ... + + let handle = thread::spawn(|| expensive_sum(my_vector)); // While the child thread is running, the main thread will also do some work for letter in vec!["a", "b", "c", "d", "e", "f"] { @@ -37,63 +37,96 @@ fn main() { // to a variable named `result` // - Get the i32 out of `result` and store it in a `sum` variable. - // let result = - // let sum = - // println!("The child thread's expensive sum is {}", sum); + let result = handle.join(); + let sum = result.unwrap(); + println!("The child thread's expensive sum is {}", sum); // 3. Time for some fun with channels! // - Uncomment the block comment below (Find and remove the `/*` and `*/`). // - Create variables `tx` and `rx` and assign them to the sending and receiving ends of an // unbounded channel. Hint: An unbounded channel can be created with `channel::unbounded()` - /* - // let ... + let (tx, rx) = channel::unbounded(); - // Cloning a channel makes another variable connected to that end of the channel so that you can - // send it to another thread. We want another variable that can be used for sending... - let tx2 = tx.clone(); + // Cloning a channel makes another variable connected to that end of the channel so that you can + // send it to another thread. We want another variable that can be used for sending... + let tx2 = tx.clone(); - // 4. Examine the flow of execution of "Thread A" and "Thread B" below. Do you see how their - // output will mix with each other? - // - Run this code. Notice the order of output from Thread A and Thread B. - // - Increase the value passed to the first `sleep_ms()` call in Thread A so that both the - // Thread B outputs occur *before* Thread A outputs anything. - // - Run the code again and make sure the output comes in a different order. + // 4. Examine the flow of execution of "Thread A" and "Thread B" below. Do you see how their + // output will mix with each other? + // - Run this code. Notice the order of output from Thread A and Thread B. + // - Increase the value passed to the first `sleep_ms()` call in Thread A so that both the + // Thread B outputs occur *before* Thread A outputs anything. + // - Run the code again and make sure the output comes in a different order. - // Thread A - let handle_a = thread::spawn(move || { - sleep_ms(0); - tx2.send("Thread A: 1").unwrap(); - sleep_ms(200); - tx2.send("Thread A: 2").unwrap(); - }); + // Thread A + let handle_a = thread::spawn(move || { + sleep_ms(500); + tx2.send("Thread A: 1").unwrap(); + sleep_ms(200); + tx2.send("Thread A: 2").unwrap(); + }); - sleep_ms(100); // Make sure Thread A has time to get going before we spawn Thread B + sleep_ms(100); // Make sure Thread A has time to get going before we spawn Thread B - // Thread B - let handle_b = thread::spawn(move || { - sleep_ms(0); - tx.send("Thread B: 1").unwrap(); - sleep_ms(200); - tx.send("Thread B: 2").unwrap(); - }); + // Thread B + let handle_b = thread::spawn(move || { + sleep_ms(0); + tx.send("Thread B: 1").unwrap(); + sleep_ms(200); + tx.send("Thread B: 2").unwrap(); + }); - // Using a Receiver channel as an iterator is a convenient way to get values until the channel - // gets closed. A Receiver channel is automatically closed once all Sender channels have been - // closed. Both our threads automatically close their Sender channels when they exit and the - // destructors for the channels get automatically called. - for msg in rx { - println!("Main thread: Received {}", msg); - } + // Using a Receiver channel as an iterator is a convenient way to get values until the channel + // gets closed. A Receiver channel is automatically closed once all Sender channels have been + // closed. Both our threads automatically close their Sender channels when they exit and the + // destructors for the channels get automatically called. + for msg in rx { + println!("Main thread: Received {}", msg); + } - // 5. Oops, we forgot to join "Thread A" and "Thread B". That's bad hygiene! - // - Use the thread handles to join both threads without getting any compiler warnings. - */ + // 5. Oops, we forgot to join "Thread A" and "Thread B". That's bad hygiene! + // - Use the thread handles to join both threads without getting any compiler warnings. + let _ = handle_a.join(); + let _ = handle_b.join(); // Challenge: Make two child threads and give them each a receiving end to a channel. From the // main thread loop through several values and print each out and then send it to the channel. // On the child threads print out the values you receive. Close the sending side in the main // thread by calling `drop(tx)` (assuming you named your sender channel variable `tx`). Join // the child threads. + + let (tx, rx) = channel::unbounded(); + let rx2 = rx.clone(); + + let thread_a_handle = thread::spawn(move || { + for n in rx { + println!("Thread A received: {}", n); + sleep_ms(10); + } + }); + + let thread_b_handle = thread::spawn(move || { + for n in rx2 { + println!("Thread B received: {}", n); + sleep_ms(10); + } + }); + + for number in vec![ + 1, + 2, + 3, + 4, + 5, + ] { + println!("NUMBER: {}", number); + let _ = tx.send(number); + } + drop(tx); + + let _ = thread_a_handle.join(); + let _ = thread_b_handle.join(); + println!("Main thread: Exiting.") }