How do I make a child thread exit after receiving messages from a channel? - multithreading

use crossbeam::channel;
use std::thread;
// (unrelated code...)
let (tx3, rx3) = channel::unbounded();
let rx4 = rx3.clone();
let a = vec!["apple", "orange", "banana", "watermelon"];
tx3.send(a).unwrap();
let handle_c = thread::spawn(move || {
for msg in rx3 {
for item in msg {
println!("Child thread c: Received {}", item);
};
};
});
let handle_d = thread::spawn(move || {
for msg in rx4 {
for item in msg {
println!("Child thread d: Received {}", item);
};
};
});
handle_c.join().unwrap();
handle_d.join().unwrap();
The idea is that Child thread c would print each string in vector a, exit, then Child thread d would print each string in vector a and exit.
However, Child thread c doesn't exit after printing everything it received and Child thread d never gets to print anything. How can I make Child thread c exit?
I've tried dropping rx3 but that doesn't work (it tried to access rx3 again). I've also searched for how to kill a child thread, but apparently there isn't a way.

Child thread c doesn't exit after printing everything it received
That is because it could still theoretically receive messages since the sender, tx3 is still alive and in scope. To close the channel, you need to ensure that all senders have been destroyed. You can do that simply by dropping it:
// close the channel
drop(tx3);
// then the for-loops in the spawned threads will end
handle_c.join().unwrap();
handle_d.join().unwrap();
Child thread d never gets to print anything
Funny that when I ran it, it was thread d that printed everything while thread c did nothing. So your behavior is not deterministic which thread will receive which message because the crossbeam channel will not duplicate the message across the receivers.
You'll need something typically called a "broadcast channel" or "fan-out queue" or variations thereof. I spend too much time in the async world so I only have experience with Tokio's broadcast module. However, #cafce25 has mentioned the bus crate as a potential solution:
use bus::Bus;
use std::thread;
fn main() {
let mut bus = Bus::new(10);
let rx1 = bus.add_rx();
let rx2 = bus.add_rx();
let a = vec!["apple", "orange", "banana", "watermelon"];
bus.broadcast(a);
let handle_c = thread::spawn(move || {
for msg in rx1 {
for item in msg {
println!("Child thread c: Received {}", item);
}
}
});
let handle_d = thread::spawn(move || {
for msg in rx2 {
for item in msg {
println!("Child thread d: Received {}", item);
}
}
});
drop(bus);
handle_c.join().unwrap();
handle_d.join().unwrap();
}
Child thread c: Received apple
Child thread d: Received apple
Child thread c: Received orange
Child thread c: Received banana
Child thread c: Received watermelon
Child thread d: Received orange
Child thread d: Received banana
Child thread d: Received watermelon

Related

Rust waiting for Tokio threads to finish

I'm trying to create a little program that will kick off new threads whenever an MPSC channel receives a message. I'm able to get the program to kick off the threads, but can't figure out how to get to the program to stay open.
Here is the app:
#[tokio::main]
async fn main() {
let (sender, mut receiver) = mpsc::channel(100);
let handles = Arc::new(tokio::sync::Mutex::new(vec![]));
let handles_clone = handles.clone();
let consumer_task = tokio::spawn(async move {
while let Some(item) = receiver.recv().await {
println!("Consumer received: {}", item);
let h = tokio::spawn(async move {
println!("Thread {} working", item);
sleep(Duration::from_secs(2)).await;
println!("Thread {} done", item);
});
{
let mut handles = handles_clone.lock().await;
handles.push(h);
}
}
});
let producer_task = tokio::spawn(async move {
for i in 0..5 {
let _ = sender.send(i).await;
}
});
let mut handles = handles.lock().await;
handles.push(consumer_task);
handles.push(producer_task);
let a = handles.deref();
join_all(a);
My thought is that I need to join_all on the handles that I'm kicking off, but am having trouble figuring out how I can push handles onto the vector inside the receiver thread, AND then join_all at the end of the program to block until those threads are done.
I wrapping the handles vec in an Arc<Mutex<_>> since I want to reference the handles vec inside the receiver thread, but I can't seem to join_all at the end, and get this error:
`&tokio::task::JoinHandle<()>` is not a future
the trait `futures::Future` is not implemented for `&tokio::task::JoinHandle<()>`
&tokio::task::JoinHandle<()> must be a future or must implement `IntoFuture` to be awaited
the trait `futures::Future` is implemented for `tokio::task::JoinHandle<T>`
`futures::Future` is implemented for `&mut tokio::task::JoinHandle<()>`, but not for `&tokio::task::JoinHandle<()>`
Any help would be greatly appreciated.
Do you see that & in the error message? The compiler error comes from the fact you don't own the JoinHandles when you call join_all (JoinHandles can only be joined once, and thus they need to be owned to do that.) You can fix that error by replacing the last two lines with:
join_all(handles.drain(..)).await;
but then you race into a deadlock: The consumer task will try to lock handles_clone, but that is locked by the "main" task, waiting in join_all, which will never finish. You could circumvent this problem by locking, popping handles, and awaiting one by one, but you'd essentially have implemented an inefficient (lifo) channel by doing so. It would be better to actually use a channel, then.
Better yet, though, to make your life easier by moving the handles Vec into your consumer task and joining there. That makes the mutex unnecessary.
let (sender, mut receiver) = mpsc::channel(100);
let consumer_task = tokio::spawn(async move {
let mut handles = vec![];
while let Some(item) = receiver.recv().await {
println!("Consumer received: {}", item);
let h = tokio::spawn(async move {
println!("Thread {} working", item);
sleep(Duration::from_secs(2)).await;
println!("Thread {} done", item);
});
handles.push(h);
}
join_all(handles).await;
});
let producer_task = tokio::spawn(async move {
for i in 0..5 {
let _ = sender.send(i).await;
}
});
consumer_task.await.unwrap();
producer_task.await.unwrap();

Rust Tokio mpsc::channel unexpected behavior for multi-task program

In the following program I use Tokio's mpsc channels. The Sender is moved to a task named input_message and the Receiver is moved to another task named printer. Both tasks are tokio::spawn()-ed in the main function. The input_message task is to read the user's input and send it through a Channel. The printer task recv() on the channel to get the user's input and simply prints it to stdout:
use std::error::Error;
use tokio::sync::mpsc;
use std::io::{BufRead, Write};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let (tx, mut rx) = mpsc::unbounded_channel::<String>();
let printer = tokio::spawn(async move {
loop {
let res = rx.recv().await; // (11) Comment this ..
// let res = rx.try_recv(); // (12) Uncomment this ,,
if let Some(m) = res { // .. and this
// if let Ok(m) = res { // ,, and this
if m.trim() == "q".to_string() {
break;
}
println!("Received: {}", m.trim());
}
}
println!("Printer exited");
});
let input_message = tokio::spawn(async move {
let stdin = std::io::stdin();
let mut bufr = std::io::BufReader::new(stdin);
let mut buf = String::new();
loop {
// Let the printer thread print the string before asking the user's input.
std::thread::sleep(std::time::Duration::from_millis(1));
print!("Enter input: ");
std::io::stdout().flush().unwrap();
bufr.read_line(&mut buf).unwrap();
if buf.trim() == "q".to_string() {
tx.send(buf).unwrap();
break;
}
tx.send(buf).unwrap();
buf = String::new();
}
println!("InputMessage exited");
});
tokio::join!(input_message, printer);
Ok(())
}
The expected behavior of the program is to:
Ask the user a random input (q to quit)
Print that same input to stdout
Using rx.recv().await as in line 11-13 the program seems to buffer the Strings representing the user's input: the various inputs are not received by the printer task that therefore does not print the strings to stdout. Once the quit message (i.e. q) is sent, the input_message task exits and the messages seems to be flushed out of the channel and the receiver processes them all at once, and so the printer task prints all the inputs at once. Here's an example of wrong output:
Enter input: Hello
Enter input: World
Enter input: q
InputMessage exited
Received: Hello
Received: World
Printer exited
My question here is, how is it possible that the channel buffers the messages and processes them in one go only when the sending thread exits, instead of receiving them as they are sent?
What I tried to do is to use the try_recv() function as in line 12-14 and indeed it fixes the problem. The output is correctly printed, here is an example:
Enter input: Hello
Received: Hello
Enter input: World
Received: World
Enter input: q
InputMessage exited
Printer exited
In light of this, I get confused. I get the difference between the recv().await and the try_recv() functions but I think there's something more in this case that I'm ignoring that makes the latter work and the former not work. Is anybody able to shed some light and elaborate on this? Why does try_recv() work and recv().await not, and why should recv().await not work in this scenario? In terms of efficiency is looping on try_recv() bad or "bad practice" at all?
There are a few things to point out here, but first of all, you are waiting for lines on std::io::stdin() which blocks the thread until a line arrives on that stream. While the thread waiting for input, no other future can be executed on this thread, this blog post is a great resource if you want to dive deeper why you shouldn't do that.
Tokio's io module offers an async handle to stdin(), you can work with this as a quick fix, although the documentation explicitly mentions that you should spin up a dedicated (non-async) thread for interactive user input instead of using the async handle.
Swapping std::io::stdin() for tokio::io::stdin() also entails swapping out the standard library BufReader for tokio's implementation that wraps an R: AsyncRead rather than R: Read.
To prevent interleaved writes between the input task and the output task, you can use a responder channel that signals to the input task when the output has been printed. Instead of sending String over the channel, you could send a Message with these fields:
struct Message {
payload: String,
done_tx: oneshot::Sender<()>,
}
After reading an input line, send the Message over the channel to the printer task. The printer task prints the String and signals through the done_tx that the input task can print the input prompt and wait for a new line.
Putting all that together with some other changes like a while loop to wait for messages, you'd end up with something like this:
use std::error::Error;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
use tokio::sync::{mpsc, oneshot};
#[derive(Debug)]
struct Message {
done_tx: oneshot::Sender<()>,
message: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let (tx, mut rx) = mpsc::unbounded_channel::<Message>();
let printer = tokio::spawn(async move {
while let Some(Message {
message: m,
done_tx,
}) = rx.recv().await
{
if m.trim() == "q".to_string() {
break;
}
println!("Received: {}", m.trim());
done_tx.send(()).unwrap();
}
println!("Printer exited");
});
let input_message = tokio::spawn(async move {
let stdin = tokio::io::stdin();
let mut stdout = tokio::io::stdout();
let mut bufr = tokio::io::BufReader::new(stdin);
let mut buf = String::new();
loop {
// Let the printer thread print the string before asking the user's input.
stdout.write(b"Enter input: ").await.unwrap();
stdout.flush().await.unwrap();
bufr.read_line(&mut buf).await.unwrap();
let end = buf.trim() == "q";
let (done_tx, done) = oneshot::channel();
let message = Message {
message: buf,
done_tx,
};
tx.send(message).unwrap();
if end {
break;
}
done.await.unwrap();
buf = String::new();
}
println!("InputMessage exited");
});
tokio::join!(input_message, printer);
Ok(())
}

Worker threads send many messages through a channel to main but only the first one is delivered

I've been trying to extend the thread pool example from the Multi-Threaded Web Server chapter in The Book. The original example works fine and dispatches messages to workers properly though the spsc channel (ingress), but now I want to return values (strings) from the worker threads through an mpsc channel (egress). Somehow the egress channel sends only one message instead of 10. egress_tx.send() seems to be executed 10 times but egress_rx.recv() gives me one message only and then the program finishes (i.e. no deadlocks etc). The worker threads are terminated properly in the Drop trait implementation (this code is not shown). I'd appreciate any suggestions about debugging such a problem: putting a breakpoint ar recv() and trying to find something meaningful in its internals hasn't helped much.
type Job = Box<dyn FnOnce(usize) -> String + Send + 'static>;
enum Message {
Run(Job),
Halt,
}
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
pub struct ThreadPool {
workers: Vec<Worker>,
ingress_tx: Sender<Message>,
pub egress_rx: Receiver<String>
}
impl Worker {
fn new(id: usize, rx: Arc<Mutex<mpsc::Receiver<Message>>>, tx: mpsc::Sender<String>) -> Worker {
let thread = thread::spawn(move ||
loop {
let msg = rx.lock().unwrap().recv().unwrap();
match msg {
Message::Run(job) => {
let s = job(id);
println!("Sending \"{}\"", s);
tx.send(s).unwrap();
},
Message::Halt => break,
}
}
);
Worker {id, thread: Some(thread)}
}
}
impl ThreadPool {
pub fn new(size: usize) -> Result<ThreadPool, ThreadPoolError> {
if size <= 0 {
return Err(ThreadPoolError::ZeroSizedPool)
}
let (ingress_tx, ingress_rx) = mpsc::channel();
let ingress_rx = Arc::new(Mutex::new(ingress_rx));
let (egress_tx, egress_rx) = mpsc::channel();
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, ingress_rx.clone(), egress_tx.clone()));
}
Ok(ThreadPool {workers, ingress_tx, egress_rx})
}
pub fn execute<F>(&self, f: F)
where F: FnOnce(usize) -> String + Send + 'static
{
let j = Box::new(f);
self.ingress_tx.send(Message::Run(j)).unwrap();
}
}
fn run_me(id: usize, i: usize) -> String {
format!("Worker {} is processing tile {}...", id, i).to_string()
}
#[cfg(test)]
mod threadpool_tests {
use super::*;
#[test]
fn tp_test() {
let tpool = ThreadPool::new(4).expect("Cannot create threadpool");
for i in 0..10 {
let closure = move |worker_id| run_me(worker_id, i);
tpool.execute(closure);
}
for s in tpool.egress_rx.recv() {
println!("{}", s);
}
}
}
And the output is:
Sending "Worker 0 is processing tile 0..."
Sending "Worker 0 is processing tile 2..."
Sending "Worker 3 is processing tile 1..."
Sending "Worker 3 is processing tile 4..."
Sending "Worker 2 is processing tile 3..."
Sending "Worker 2 is processing tile 6..."
Sending "Worker 1 is processing tile 5..."
Sending "Worker 0 is processing tile 7..."
Sending "Worker 0 is processing tile 9..."
Sending "Worker 3 is processing tile 8..."
Receiving "Worker 0 is processing tile 0..."
Process finished with exit code 0
In your code, you have for s in tpool.egress_rx.recv(), which isn't doing quite what you want. Instead of iterating over the values received by the channel, you're receiving one element (wrapped in a Result) and then iterating over that, since Result implements IntoIterator to iterate over the success value (or nothing, if it contains an error).
Simply changing this to for s in tpool.egress_rx should fix it, since channels also implement IntoIterator.

Unable to use asynchronous actors

I'm trying to use the actors as documented in the actix documentation. But even the doc example is not working for me. I tried the following code which compiles but does not print the message "Received fibo message"
use actix::prelude::*;
// #[derive(Message)]
// #[rtype(Result = "Result<u64, ()>")]
// struct Fibonacci(pub u32);
struct Fibonacci(pub u32);
impl Message for Fibonacci {
type Result = Result<u64, ()>;
}
struct SyncActor;
impl Actor for SyncActor {
// It's important to note that you use "SyncContext" here instead of "Context".
type Context = SyncContext<Self>;
}
impl Handler<Fibonacci> for SyncActor {
type Result = Result<u64, ()>;
fn handle(&mut self, msg: Fibonacci, _: &mut Self::Context) -> Self::Result {
println!("Received fibo message");
if msg.0 == 0 {
Err(())
} else if msg.0 == 1 {
Ok(1)
} else {
let mut i = 0;
let mut sum = 0;
let mut last = 0;
let mut curr = 1;
while i < msg.0 - 1 {
sum = last + curr;
last = curr;
curr = sum;
i += 1;
}
Ok(sum)
}
}
}
fn main() {
System::new().block_on(async {
// Start the SyncArbiter with 2 threads, and receive the address of the Actor pool.
let addr = SyncArbiter::start(2, || SyncActor);
// send 5 messages
for n in 5..10 {
// As there are 2 threads, there are at least 2 messages always being processed
// concurrently by the SyncActor.
println!("Sending fibo message");
addr.do_send(Fibonacci(n));
}
});
}
This program displays 5 times :
Sending fibo message
Two remarks, first I'm unable to use the macro rtype, I use to implement Message myself. And then the line addr.do_send(Fibonacci(n)) seems to not send anything to my actor. However if I use addr.send(Fibonacci(n)).await; my message get sent and received on the actor side. But since I'm awaiting the send function it processes the message synchronously instead of using the 2 threads I have defined theoretically.
I also tried to wait with a thread::sleep after my main loop but the messages were not arriving either.
I might be misunderstanding something but it seems strange to me.
Cargo.toml file :
[dependencies]
actix = "0.11.1"
actix-rt = "2.2.0"
I finally managed to make it works, though I can't understand exactly why. Simply using tokio to wait for a ctrl-C made it possible for me to call do_send/try_send and work in parallel.
fn main() {
System::new().block_on(async {
// Start the SyncArbiter with 4 threads, and receive the address of the Actor pool.
let addr = SyncArbiter::start(4, || SyncActor);
// send 15 messages
for n in 5..20 {
// As there are 4 threads, there are at least 4 messages always being processed
// concurrently by the SyncActor.
println!("Sending fibo message");
addr.do_send(Fibonacci(n));
}
// This does not wotk
//thread::spawn(move || {
// thread::sleep(Duration::from_secs_f32(10f32));
//}).join();
// This made it worked
tokio::signal::ctrl_c().await.unwrap();
println!("Ctrl-C received, shutting down");
System::current().stop();
});
}
You don't have to use crate tokio explicitly here. In your loop, just change the last line to addr.send(Fibonacci(n)).await.unwrap(). Method send returns a future and it must be awaited to resolve.

How do I perform cleanup in blocking child threads when the process dies?

I have the following contrived Rust code:
use std::thread;
const THREAD_COUNT: u8 = 10;
struct DropMe {
id: u8,
}
impl Drop for DropMe {
fn drop(&mut self) {
println!("Dropped item {}", self.id);
}
}
fn main() {
let outer_drop_me = DropMe { id: 255 };
println!(
"Created instance outside of threads with ID: {}",
&outer_drop_me.id
);
for i in 0..THREAD_COUNT {
let drop_me = DropMe { id: i };
thread::spawn(move || {
println!("Spawned thread {}", drop_me.id);
// Poor man's substitute for illustrating blocking I/O
thread::sleep(std::time::Duration::from_millis(500));
// Poor man's substitute for illustrating a cleanup function
drop(drop_me);
});
}
// outer_drop_me should be dropped automatically here as it goes out of
// scope
}
The output of which is as follows:
Created instance outside of threads with ID: 255
Spawned thread 0
Spawned thread 1
Spawned thread 2
Spawned thread 3
Spawned thread 4
Spawned thread 5
Spawned thread 6
Spawned thread 7
Spawned thread 8
Dropped item 255
Spawned thread 9
How can I do cleanup in threads where the code inside those threads may be blocking, but the process is being terminated (e.g. by way of SIGTERM)?
In this contrived example, one could join on the returned thread handles from the spawned threads, but what if join is not available and the child threads are blocking? Do you just resign yourself to forfeiting the cleanup code after the part of the code that may be blocking?

Resources