In its default form, this example compiles and the single sender can send messages without errors. Only one receiver is able to receive the message at any given time and it seems to be the one thread that the OS chooses as it sees fit. This is expected according the documentation of crossbeam-channel: Note that cloning only creates a new handle to the same sending or receiving side. It does not create a separate stream of messages in any way:
use std::{thread, time::Duration};
use rand::{rng, seq::IndexedRandom} ;
use crossbeam_channel::{bounded, Receiver};
trait Kid {
fn start(&self, name: String, receiver: Receiver<String>) {
thread::spawn(move || {
loop {
println!("{name} is doing daily chores and collects spores.");
if let Ok(message) = receiver.recv() {
println!("{name} received: {message}");
} else {
println!("{name} never received but deceived.");
break;
}
thread::sleep(Duration::from_millis(750));
}
});
}
}
struct NephewAndrew {}
impl Kid for NephewAndrew {}
struct NephewMatthew {}
impl Kid for NephewMatthew {}
struct NieceAnise {}
impl Kid for NieceAnise {}
struct UncleMonocle {
i_am: String,
}
impl UncleMonocle {
pub fn start(&self) -> thread::JoinHandle<()> {
let (sender, receiver) = bounded(1);
let andrew = NephewAndrew {};
andrew.start("Nephew Andrew".to_owned(), receiver.clone());
let matthew = NephewMatthew {};
matthew.start("Nephew Matthew".to_owned(), receiver.clone());
let anise = NieceAnise {};
anise.start("Niece Anise".to_owned(), receiver.clone());
let uncle_monocle_says = [
"Comment Component",
"Trust Rust",
"Knightly Night",
"Election Selection",
"Scrap Crap",
"Spray Pray",
"Tribe Scribe",
"Message Passage",
"Power Powder",
"Science Conscience"
];
let name = self.i_am.clone();
thread::spawn(move || {
let mut rng = rng();
let mut count = 0;
while count < 10 {
println!("-------------------------");
println!("{name} does daily errands and spends ten grands.");
let i_say = uncle_monocle_says.choose(&mut rng).unwrap();
println!("{name} says: {i_say}");
sender.send(i_say.to_string()).expect("{name}: Message passage failed and flailed while sending was pending.");
thread::sleep(Duration::from_secs(1));
count += 1;
}
})
}
}
fn main() {
let um = UncleMonocle {
i_am: "Uncle Monocle".to_owned(),
};
let join_handle = um.start();
join_handle.join().unwrap();
}
Output:
Nephew Andrew is doing daily chores and collects spores.
Nephew Matthew is doing daily chores and collects spores.
Niece Anise is doing daily chores and collects spores.
-------------------------
Uncle Monocle does daily errands and spends ten grands.
Uncle Monocle says: Tribe Scribe
Niece Anise received: Tribe Scribe
Niece Anise is doing daily chores and collects spores.
-------------------------
Uncle Monocle does daily errands and spends ten grands.
Uncle Monocle says: Science Conscience
Nephew Matthew received: Science Conscience
Nephew Matthew is doing daily chores and collects spores.
-------------------------
Uncle Monocle does daily errands and spends ten grands.
Uncle Monocle says: Message Passage
Nephew Andrew received: Message Passage
Nephew Andrew is doing daily chores and collects spores.
-------------------------
Uncle Monocle does daily errands and spends ten grands.
Uncle Monocle says: Trust Rust
Niece Anise received: Trust Rust
Niece Anise is doing daily chores and collects spores.
-------------------------
Uncle Monocle does daily errands and spends ten grands.
Uncle Monocle says: Scrap Crap
Nephew Matthew received: Scrap Crap
Nephew Matthew is doing daily chores and collects spores.
-------------------------
Uncle Monocle does daily errands and spends ten grands.
Uncle Monocle says: Trust Rust
Nephew Andrew received: Trust Rust
Nephew Andrew is doing daily chores and collects spores.
-------------------------
Uncle Monocle does daily errands and spends ten grands.
Uncle Monocle says: Science Conscience
Niece Anise received: Science Conscience
Niece Anise is doing daily chores and collects spores.
-------------------------
Uncle Monocle does daily errands and spends ten grands.
Uncle Monocle says: Tribe Scribe
Nephew Matthew received: Tribe Scribe
Nephew Matthew is doing daily chores and collects spores.
-------------------------
Uncle Monocle does daily errands and spends ten grands.
Uncle Monocle says: Scrap Crap
Nephew Andrew received: Scrap Crap
Nephew Andrew is doing daily chores and collects spores.
-------------------------
Uncle Monocle does daily errands and spends ten grands.
Uncle Monocle says: Scrap Crap
Niece Anise received: Scrap Crap
Niece Anise is doing daily chores and collects spores.
Nephew Matthew never received but deceived.
Nephew Andrew never received but deceived.
Niece Anise never received but deceived.
Errors:
Compiling playground v0.0.1 (/playground)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.94s
Running `target/debug/playground`
When I change the sending code to use a 3 times loop and send the message 3 times, then every receiver receives the message. This seems to be the fix:
My questions are:
- Why is the crossbeam designed this way? Why isn't there an internal mechanism that keeps track of receiver numbers and just sends the message in a loop itself (instead of cloning the message contents in client code n times).
- If the answer to the previous question is default performance and simplicity, then why isn't there an extra API that implements this broadcast functionality optionally? We only pay for what we use right?
- I can understand the message not being received by all receivers, because there wasn't 3 messages in the pipeline to begin with, there was only one. But can't understand why receivers don't proceed to do their daily chores if there's no message for them? In the output of first (broken) version I see the only thread that does other work (chores), is the thread that successfully receives the message.
- What does
bounded
andunbounded
mean in real terms? What's the difference between bounded(1), bounded(5) and unbounded in the context of this example?
Thanks