I would like to know how to create a deadlock.
I tried to create a program in Rust that has a deadlock.
How to create one?
A very simple variant:
use std::sync::{Arc, Mutex};
fn main() {
let data = Arc::new(Mutex::new(0));
let d1 = data.lock();
let d2 = data.lock(); // cannot lock, since d1 is still active
}
Same as in other programming languages, Rust can have silent killer deadlocks.
If some thread locking a mutex is waiting for another, this is not a good sign: if that other cannot come, the first mutex will never be released.
use std::{sync::{Mutex, MutexGuard}, thread};
use std::thread::sleep;
use std::time::Duration;
use lazy_static::lazy_static;
lazy_static! {
static ref MUTEX1: Mutex<i64> = Mutex::new(0);
static ref MUTEX2: Mutex<i64> = Mutex::new(0);
}
fn main() {
// Spawn thread and store handles
let mut children = vec![];
for i_thread in 0..2 {
children.push(thread::spawn(move || {
for _ in 0..1 {
// Thread 1
if i_thread % 2 == 0 {
// Lock mutex1
// No need to specify type but yes create a dummy variable to prevent rust
// compiler from being lazy
let _guard: MutexGuard<i64> = MUTEX1.lock().unwrap();
// Just log
println!("Thread {} locked mutex1 and will try to lock the mutex2, after a nap !", i_thread);
// Here I sleep to let Thread 2 lock mutex2
sleep(Duration::from_millis(10));
// Lock mutex 2
let _guard = MUTEX2.lock().unwrap();
// Thread 2
} else {
// Lock mutex 1
let _guard = MUTEX2.lock().unwrap();
println!("Thread {} locked mutex2 and will try to lock the mutex1", i_thread);
// Here I freeze !
let _guard = MUTEX1.lock().unwrap();
}
}
}));
}
// Wait
for child in children {
let _ = child.join();
}
println!("This is not printed");
}
Which outputs
Thread 0 locked mutex1 and will try to lock the mutex2, after a nap !
Thread 1 locked mutex2 and will try to lock the mutex1
and then waits forever
Related
I've read this Turning Our Single-Threaded Server into a Multithreaded Server.
And tried to implement it.
I wrote this:
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
type task = dyn FnOnce() + Send + 'static;
pub struct Threadpool {
threads: Vec<thread::JoinHandle<()>>,
rx: Arc<Mutex<Receiver<Box<task>>>>,
tx: Sender<Box<task>>,
}
impl Threadpool {
pub fn new(size: usize) -> Threadpool {
let mut tasks = Vec::with_capacity(size);
let (tx, rx): (Sender<Box<task>>, Receiver<Box<task>>) = channel();
let rx = Arc::new(Mutex::new(rx));
for _ in 0..size {
let rx = rx.clone();
let task = thread::spawn(move || {
loop {
let job= rx.lock().unwrap().recv().unwrap();
job();
}
});
tasks.push(task);
}
Threadpool {
threads: tasks,
rx,
tx,
}
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
self.tx.send(Box::new(f)).unwrap();
}
}
It works.
But when I change
let job= rx.lock().unwrap().recv().unwrap();
job();
to
rx.lock().unwrap().recv().unwrap()();
When I open localhost:port/sleep, and then open localhost:port, it will takes 5 seconds.
I set this in main
"GET /sleep HTTP/1.1" => {
thread::sleep(Duration::from_secs(5));
("HTTP/1.1 200 OK", "hello.html")
}
I already knew that while let will cause that.
But I can't figure out why my code above will also lead to that.
Can anybody give me the answer.
In Rust temporary objects are dropped at the end of the expression that contains them (with a few caveats not relevant here).
And the temporary we are interested in is the guard of the mutex, whose drop is responsible of releasing the mutex lock.
So, writingh the drop explicitly, your first code:
let job = rx.lock().unwrap().recv().unwrap();
job();
Is equivalent to:
let mut guard = rx.lock().unwrap();
let job = guard.recv().unwrap();
drop(guard);
job();
And your second code:
rx.lock().unwrap().recv().unwrap()();
Is equivalent to:
let mut guard = rx.lock().unwrap();
let job = guard.recv().unwrap()
job();
drop(guard);
As you can see, now you are calling the job() function with the mutex still locked.
Is this because the mutex was not released?
Yes, you are basically doing this
{
let rx = rx.lock().unwrap(); // got the lock
let job = rx.recv().unwrap(); // got the job
// going to sleep while still holding mutex lock
job(); // std::thread::sleep(Duration::from_secs(5))
drop(rx); // lock is released
}
As all the threads share the mutex, and will try to acquire lock, they are effectively blocked until sleeping thread with lock wakes up. Which is why after requesting sleep endpoint, other threads are unable to perform any other job.
There's another issue however. Even if it didn't sleep, it still calls Receiver::recv() while still holding the lock, which blocks the current thread (goes to sleep) until something is sent down the channel. But considering that one thread will only block others if there's no jobs on the channel, I guess that is by design.
I am trying to run 2 threads in parallel and share some data between them. When either one of the threads contain a loop statement, the shared data in the other thread goes into a deadlock.
But if I were to add a line to code to break out of the loop statement after a certain number of iterations, the deadlock gets released and the operation in the next thread starts.
Rust Playground
Code:
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
#[derive(Clone, Copy)]
struct SomeNetwork {
is_connected: bool,
}
impl SomeNetwork {
fn connection_manager(&mut self) {
loop {
// if I exit the loop after a few iterations then the deadlock is removed
// eg: when I use `for i in 0..10 {` instead of `loop`
println!("connection_manager thread...");
thread::sleep(Duration::from_millis(2000));
}
}
fn api_calls(&self) {
loop {
if self.is_connected {
//make_an_api_call()
}
println!("api_calls thread...");
thread::sleep(Duration::from_millis(5000));
}
}
pub fn start() {
let self_arc = SomeNetwork {
is_connected: false,
};
let self_arc = Arc::new(Mutex::new(self_arc));
let self_cloned1 = Arc::clone(&self_arc);
let self_cloned2 = Arc::clone(&self_arc);
thread::Builder::new()
.spawn(move || {
let mut n = self_cloned1.lock().unwrap();
n.connection_manager();
})
.unwrap();
thread::Builder::new()
.spawn(move || {
let n = self_cloned2.lock().unwrap(); // <---- deadlock here
n.api_calls();
})
.unwrap();
loop {
thread::sleep(Duration::from_millis(5000))
}
}
}
fn main() {
SomeNetwork::start();
}
Output:
connection_manager thread...
connection_manager thread...
connection_manager thread...
connection_manager thread...
connection_manager thread...
....
Wouldn't the underlying OS take care of the scheduling once a thread goes into sleep?
What could be done here, so that I can run both threads in parallel?
The issue is the mutex you created stays locked during connection_manager.
The way you use a mutex in Rust is that it wraps the data it locks. When you lock the mutex, it blocks the current thread until it can obtain the mutex. Once it has, it gives you a MutexGuard which you can think of as a wrapper for a reference to the mutex. The MutexGuard gives you mutable access to the data inside the mutex. Then once the MutexGuard is no longer needed Rust invokes MutexGuard's implementation of Drop which unlocks the mutex and allows other threads to obtain it.
// Block until mutex is locked for this thread and return MutexGuard
let mut n = self_cloned1.lock().unwrap();
// Do stuff with the locked mutex
n.connection_manager();
// MutexGuard is no longer needed so it gets dropped and the mutex is released
As you can see, if connection_manager never exits the mutex will remain locked for the first thread to obtain the mutex.
What you want is probably to use a mutex with a condvar so the mutex can be released while the thread is sleeping.
Edit:
Here is a rough idea of what that using condvars to handle connecting and channels to pass jobs to workers would look like. Playground Link
use std::sync::{Arc, Mutex, Condvar};
use std::thread::{self, current};
use std::time::Duration;
use crossbeam_channel::{unbounded, Receiver};
#[derive(Clone, Copy)]
struct SomeNetwork {
is_connected: bool,
}
const TIMEOUT: Duration = Duration::from_secs(5);
impl SomeNetwork {
fn connect(&mut self) {
println!("connection_manager thread...");
self.is_connected = true;
}
fn api_calls(&self, job: i32) {
//println!("api_calls thread...");
println!("[Worker {:?}] Handling job {}", current().id(), job);
thread::sleep(Duration::from_millis(50))
}
pub fn start_connection_thread(
self_data: Arc<Mutex<Self>>,
connect_condvar: Arc<Condvar>,
worker_condvar: Arc<Condvar>,
) {
thread::Builder::new()
.spawn(move || {
let mut guard = self_data.lock().unwrap();
loop {
// Do something with the data
if !guard.is_connected {
guard.connect();
// Notify all workers that the connection is ready
worker_condvar.notify_all();
}
// Use condvar to release mutex and wait until signaled to start again
let (new_guard, _) = connect_condvar.wait_timeout(guard, TIMEOUT).unwrap();
guard = new_guard;
}
})
.unwrap();
}
pub fn start_worker_thread(
self_data: Arc<Mutex<Self>>,
connect_condvar: Arc<Condvar>,
worker_condvar: Arc<Condvar>,
requests: Receiver<i32>,
) {
thread::Builder::new()
.spawn(move || {
loop {
// Wait until a request is received
let request = requests.recv().unwrap();
// Lock mutex once we have a request
let mut guard = self_data.lock().unwrap();
// Make sure we are connected before starting tasks
while !guard.is_connected {
// Wake up 1 connection thread if the connection breaks
connect_condvar.notify_one();
// Sleep until signaled that the connection has been fixed
let (new_guard, _) = worker_condvar.wait_timeout(guard, TIMEOUT).unwrap();
guard = new_guard;
}
// Now that we have verified we are connected, handle the request
guard.api_calls(request);
}
})
.unwrap();
}
pub fn start() {
let self_arc = SomeNetwork {
is_connected: false,
};
let self_arc = Arc::new(Mutex::new(self_arc));
let connect_condvar = Arc::new(Condvar::new());
let worker_condvar = Arc::new(Condvar::new());
// Create a channel to send jobs to workers
let (send, recv) = unbounded();
Self::start_connection_thread(self_arc.clone(), connect_condvar.clone(), worker_condvar.clone());
// Start some workers
for _ in 0..5 {
Self::start_worker_thread(self_arc.clone(), connect_condvar.clone(), worker_condvar.clone(), recv.clone());
}
// Send messages to workers
for message in 1..100 {
send.send(message);
}
loop {
thread::sleep(Duration::from_millis(5000))
}
}
}
fn main() {
SomeNetwork::start();
}
I'm new to Rust. I'm supposed to use a Mutex and an Arc to create a critical section within the print_lots function to stop the race condition from happening. Any Ideas?
fn main() {
let num_of_threads = 4;
let mut array_of_threads = vec![];
for id in 0..num_of_threads {
array_of_threads.push(std::thread::spawn(move || print_lots(id)));
}
for t in array_of_threads {
t.join().expect("Thread join failure");
}
}
fn print_lots(id: u32) {
println!("Begin [{}]", id);
for _i in 0..100 {
print!("{} ", id);
}
println!("\nEnd [{}]", id);
}
Mutex in Rust perhaps works differently to how locks work in some other languages you might be used to. Instead of tracking the lock independently from the value, a Rust Mutex owns the data and prevents accessing it without first obtaining a lock, which is enforced at compile time.
The warning you are getting is because you have locked the Mutex, but then done nothing with the value. The warning is there because this is almost certainly a mistake.
fn main() {
let foo = Mutex::new(0);
// It's often best to just unwrap and panic if the lock is poisoned
if let Ok(mut lock) = foo.lock() {
*lock = 2;
// The mutex is unlocked automatically when lock goes out of scope here
}
println!("{:?}", foo); // Mutex { data: 2 }
}
I am guessing that your real problem is that you want to synchronise the print statements so that output from different threads is not intermingled.
One way to do that is to obtain a lock on StdOut which actually uses a lock internally and provides a similar API to Mutex:
fn print_lots(id: u32) {
let stdout = io::stdout();
println!("Begin [{}]", id);
let mut handle = stdout.lock();
for _i in 0..100 {
write!(&mut handle, "{} ", id).unwrap();
}
println!("\nEnd [{}]", id);
// handle is dropped here, unlocking stdout
}
In your simplified example, creating a long-lived lock in each thread is counterproductive since each thread will block the others and the result is sequential rather than concurrent. This might still make sense though if your real-world code has more going on.
use std::sync::{Arc, Mutex};
fn main() {
let num_of_threads = 4;
let mut array_of_threads = vec![];
let counter = Arc::new(Mutex::new(0));
for id in 0..num_of_threads {
let counter_clone = counter.clone();
array_of_threads.push(std::thread::spawn(move || print_lots(id, counter_clone)));
}
for t in array_of_threads {
t.join().expect("Thread join failure");
}
}
fn print_lots(id: u32, c: Arc<Mutex<u32>>) {
println!("Begin [{}]", id);
let _guard = c.lock().unwrap();
for _i in 0..100 {
print!("{} ", id);
}
println!("\nEnd [{}]", id);
}
I wanted to write a program that spawns two threads that lock a Mutex, increase it, print something, and then unlock the Mutex so the other thread can do the same. I added some sleep time to make it more consistent, so I thought the output should be something like:
ping pong ping pong …
but the actual output is pretty random. Most of the time, it is just
ping ping ping … pong
But there's no consistency at all; sometimes there is a “pong” in the middle too.
I was of the belief that mutexes had some kind of way to determine who wanted to lock it last but it doesn’t look like that’s the case.
How does the locking actually work?
How can I get the desired output?
use std::sync::{Arc, Mutex};
use std::{thread, time};
fn main() {
let data1 = Arc::new(Mutex::new(1));
let data2 = data1.clone();
let ten_millis = time::Duration::from_millis(10);
let a = thread::spawn(move || loop {
let mut data = data1.lock().unwrap();
thread::sleep(ten_millis);
println!("ping ");
*data += 1;
if *data > 10 {
break;
}
});
let b = thread::spawn(move || loop {
let mut data = data2.lock().unwrap();
thread::sleep(ten_millis);
println!("pong ");
*data += 1;
if *data > 10 {
break;
}
});
a.join().unwrap();
b.join().unwrap();
}
Mutex and RwLock both defer to OS-specific primitives and cannot be guaranteed to be fair. On Windows, they are both implemented with SRW locks which are specifically documented as not fair. I didn't do research for other operating systems but you definitely cannot rely on fairness with std::sync::Mutex, especially if you need this code to be portable.
A possible solution in Rust is the Mutex implementation provided by the parking_lot crate, which provides an unlock_fair method, which is implemented with a fair algorithm.
From the parking_lot documentation:
By default, mutexes are unfair and allow the current thread to re-lock the mutex before another has the chance to acquire the lock, even if that thread has been blocked on the mutex for a long time. This is the default because it allows much higher throughput as it avoids forcing a context switch on every mutex unlock. This can result in one thread acquiring a mutex many more times than other threads.
However in some cases it can be beneficial to ensure fairness by forcing the lock to pass on to a waiting thread if there is one. This is done by using this method instead of dropping the MutexGuard normally.
While parking_lot::Mutex doesn't claim to be fair without specifically using the unlock_fair method, I found that your code produced the same number of pings as pongs, by just making that switch (playground), not even using the unlock_fair method.
Usually mutexes are unlocked automatically, when a guard goes out of scope. To make it unlock fairly, you need to insert this method call before the guard is dropped:
let b = thread::spawn(move || loop {
let mut data = data1.lock();
thread::sleep(ten_millis);
println!("pong ");
*data += 1;
if *data > 10 {
break;
}
MutexGuard::unlock_fair(data);
});
The order of locking the mutex is not guaranteed in any way; it's possible for the first thread to acquire the lock 100% of the time, while the second thread 0%
The threads are scheduled by the OS and the following scenario is quite possible:
the OS gives CPU time to the first thread and it acquires the lock
the OS gives CPU time to the second thread, but the lock is taken, hence it goes to sleep
The fist thread releases the lock, but is still allowed to run by the OS. It goes for another iteration of the loop and re-acquires the lock
The other thread cannot proceed, because the lock is still taken.
If you give the second thread more time to acquire the lock you will see the expected ping-pong pattern, although there is no guarantee (a bad OS may decide to never give CPU time to some of your threads):
use std::sync::{Arc, Mutex};
use std::{thread, time};
fn main() {
let data1 = Arc::new(Mutex::new(1));
let data2 = data1.clone();
let ten_millis = time::Duration::from_millis(10);
let a = thread::spawn(move || loop {
let mut data = data1.lock().unwrap();
*data += 1;
if *data > 10 {
break;
}
drop(data);
thread::sleep(ten_millis);
println!("ping ");
});
let b = thread::spawn(move || loop {
let mut data = data2.lock().unwrap();
*data += 1;
if *data > 10 {
break;
}
drop(data);
thread::sleep(ten_millis);
println!("pong ");
});
a.join().unwrap();
b.join().unwrap();
}
You can verify that by playing with the sleep time. The lower the sleep time, the more irregular the ping-pong alternations will be, and with values as low as 10ms, you may see ping-ping-pong, etc.
Essentially, a solution based on time is bad by design. You can guarantee that "ping" will be followed by "pong" by improving the algorithm. For instance you can print "ping" on odd numbers and "pong" on even numbers:
use std::sync::{Arc, Mutex};
use std::{thread, time};
const MAX_ITER: i32 = 10;
fn main() {
let data1 = Arc::new(Mutex::new(1));
let data2 = data1.clone();
let ten_millis = time::Duration::from_millis(10);
let a = thread::spawn(move || 'outer: loop {
loop {
thread::sleep(ten_millis);
let mut data = data1.lock().unwrap();
if *data > MAX_ITER {
break 'outer;
}
if *data & 1 == 1 {
*data += 1;
println!("ping ");
break;
}
}
});
let b = thread::spawn(move || 'outer: loop {
loop {
thread::sleep(ten_millis);
let mut data = data2.lock().unwrap();
if *data > MAX_ITER {
break 'outer;
}
if *data & 1 == 0 {
*data += 1;
println!("pong ");
break;
}
}
});
a.join().unwrap();
b.join().unwrap();
}
This isn't the best implementation, but I tried to do it with as few modifications as possible to the original code.
You may also consider an implementation with a Condvar, a better solution, in my opinion, as it avoids the busy waiting on the mutex (ps: also removed the code duplication):
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
const MAX_ITER: i32 = 10;
fn main() {
let cv1 = Arc::new((Condvar::new(), Mutex::new(1)));
let cv2 = cv1.clone();
let a = thread::spawn(ping_pong_task("ping", cv1, |x| x & 1 == 1));
let b = thread::spawn(ping_pong_task("pong", cv2, |x| x & 1 == 0));
a.join().unwrap();
b.join().unwrap();
}
fn ping_pong_task<S: Into<String>>(
msg: S,
cv: Arc<(Condvar, Mutex<i32>)>,
check: impl Fn(i32) -> bool) -> impl Fn()
{
let message = msg.into();
move || {
let (condvar, mutex) = &*cv;
let mut value = mutex.lock().unwrap();
loop {
if check(*value) {
println!("{} ", message);
*value += 1;
condvar.notify_all();
}
if *value > MAX_ITER {
break;
}
value = condvar.wait(value).unwrap();
}
}
}
I was of the belief that mutexes had some kind of way to determine who wanted to lock it last but it doesn’t look like that’s the case.
Nope. The job of a mutex is just to make the code run as fast as possible. Alternation gives the worst performance because you're constantly blowing out the CPU caches. You are asking for the worst possible implementation of a mutex.
How does the locking actually work?
The scheduler tries to get as much work done as possible. It's your job to write code that only does the work you really want to get done.
How can I get the desired output?
Don't use two threads if you just want to do one thing then something else then the first thing again. Use threads when you don't care about the order in which work is done and just want to get as much work done as possible.
Editor's note — this example was created before Rust 1.0 and the specific types have changed or been removed since then. The general question and concept remains valid.
I have spawned a thread with an infinite loop and timer inside.
thread::spawn(|| {
let mut timer = Timer::new().unwrap();
let periodic = timer.periodic(Duration::milliseconds(200));
loop {
periodic.recv();
// Do my work here
}
});
After a time based on some conditions, I need to terminate this thread from another part of my program. In other words, I want to exit from the infinite loop. How can I do this correctly? Additionally, how could I to suspend this thread and resume it later?
I tried to use a global unsafe flag to break the loop, but I think this solution does not look nice.
For both terminating and suspending a thread you can use channels.
Terminated externally
On each iteration of a worker loop, we check if someone notified us through a channel. If yes or if the other end of the channel has gone out of scope we break the loop.
use std::io::{self, BufRead};
use std::sync::mpsc::{self, TryRecvError};
use std::thread;
use std::time::Duration;
fn main() {
println!("Press enter to terminate the child thread");
let (tx, rx) = mpsc::channel();
thread::spawn(move || loop {
println!("Working...");
thread::sleep(Duration::from_millis(500));
match rx.try_recv() {
Ok(_) | Err(TryRecvError::Disconnected) => {
println!("Terminating.");
break;
}
Err(TryRecvError::Empty) => {}
}
});
let mut line = String::new();
let stdin = io::stdin();
let _ = stdin.lock().read_line(&mut line);
let _ = tx.send(());
}
Suspending and resuming
We use recv() which suspends the thread until something arrives on the channel. In order to resume the thread, you need to send something through the channel; the unit value () in this case. If the transmitting end of the channel is dropped, recv() will return Err(()) - we use this to exit the loop.
use std::io::{self, BufRead};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
println!("Press enter to wake up the child thread");
let (tx, rx) = mpsc::channel();
thread::spawn(move || loop {
println!("Suspending...");
match rx.recv() {
Ok(_) => {
println!("Working...");
thread::sleep(Duration::from_millis(500));
}
Err(_) => {
println!("Terminating.");
break;
}
}
});
let mut line = String::new();
let stdin = io::stdin();
for _ in 0..4 {
let _ = stdin.lock().read_line(&mut line);
let _ = tx.send(());
}
}
Other tools
Channels are the easiest and the most natural (IMO) way to do these tasks, but not the most efficient one. There are other concurrency primitives which you can find in the std::sync module. They belong to a lower level than channels but can be more efficient in particular tasks.
The ideal solution would be a Condvar. You can use wait_timeout in the std::sync module, as pointed out by #Vladimir Matveev.
This is the example from the documentation:
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
use std::time::Duration;
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = pair.clone();
thread::spawn(move|| {
let &(ref lock, ref cvar) = &*pair2;
let mut started = lock.lock().unwrap();
*started = true;
// We notify the condvar that the value has changed.
cvar.notify_one();
});
// wait for the thread to start up
let &(ref lock, ref cvar) = &*pair;
let mut started = lock.lock().unwrap();
// as long as the value inside the `Mutex` is false, we wait
loop {
let result = cvar.wait_timeout(started, Duration::from_millis(10)).unwrap();
// 10 milliseconds have passed, or maybe the value changed!
started = result.0;
if *started == true {
// We received the notification and the value has been updated, we can leave.
break
}
}
Having been back to this question several times myself, here's what I think addresses OP's intent and others' best practice of getting the thread to stop itself. Building on the accepted answer, Crossbeam is a nice upgrade to mpsc in allowing message endpoints to be cloned and moved. It also has a convenient tick function. The real point here is it has try_recv() which is non-blocking.
I'm not sure how universally useful it'd be to put a message checker in the middle of an operational loop like this. I haven't found that Actix (or previously Akka) could really stop a thread without--as stated above--getting the thread to do it itself. So this is what I'm using for now (wide open to correction here, still learning myself).
// Cargo.toml:
// [dependencies]
// crossbeam-channel = "0.4.4"
use crossbeam_channel::{Sender, Receiver, unbounded, tick};
use std::time::{Duration, Instant};
fn main() {
let (tx, rx):(Sender<String>, Receiver<String>) = unbounded();
let rx2 = rx.clone();
// crossbeam allows clone and move of receiver
std::thread::spawn(move || {
// OP:
// let mut timer = Timer::new().unwrap();
// let periodic = timer.periodic(Duration::milliseconds(200));
let ticker: Receiver<Instant> = tick(std::time::Duration::from_millis(500));
loop {
// OP:
// periodic.recv();
crossbeam_channel::select! {
recv(ticker) -> _ => {
// OP: Do my work here
println!("Hello, work.");
// Comms Check: keep doing work?
// try_recv is non-blocking
// rx, the single consumer is clone-able in crossbeam
let try_result = rx2.try_recv();
match try_result {
Err(_e) => {},
Ok(msg) => {
match msg.as_str() {
"END_THE_WORLD" => {
println!("Ending the world.");
break;
},
_ => {},
}
},
_ => {}
}
}
}
}
});
// let work continue for 10 seconds then tell that thread to end.
std::thread::sleep(std::time::Duration::from_secs(10));
println!("Goodbye, world.");
tx.send("END_THE_WORLD".to_string());
}
Using strings as a message device is a tad cringeworthy--to me. Could do the other suspend and restart stuff there in an enum.