Alternative to manual drop() of last mpsc::Sender reference

Hello,

I use an mpsc channel to create N (known at runtime) tasks sending messages to one receiver; see Playground & code below.

Because I create the N tasks dynamically, I clone tx1 for each of them. In the end, I am therefore in possession of N+1 Sender references, N of which will get used for message-passing and eventually drop out-of-scope. Sadly, I am left with the original tx1 reference that was used to clone the other ones. But to get a well-behaved channel for which recv() returns None at the end, all Senders need to eventually drop. I therefore invoke drop(tx1).

While it looks perfectly fine to do so, it feels wrong and slightly ugly. Could you suggest better-looking code that does not involve eg. special-casing the first or last iteration of the task creation? Or is this The Right Way™?

Playground

use std::sync::mpsc::channel;
use std::thread::spawn;

fn main()  {
    let (tx1, rx) = channel();

    (0..10).for_each(|i| {
        let tx = tx1.clone();
       spawn(move || { tx.send(Some(i)).unwrap(); });
    });
    
    // How can I make this not necessary?
    drop(tx1);
    
    for i in rx.iter() {
       println!("received {:?}", i);
    }
    println!("done!");
}

Related: Decent way to drop orginal sender after multiple clones in for_each

use std::sync::mpsc::channel;
use std::thread::spawn;
use std::iter;

fn main()  {
    let (tx, rx) = channel();

    for (i, tx) in iter::repeat(tx).take(10).enumerate() {
       spawn(move || { tx.send(Some(i)).unwrap(); });
    }
    
    for i in rx {
       println!("received {:?}", i);
    }
    println!("done!");
}
4 Likes

You could make your for_each closure a move closure:

fn main()  {
    let (tx1, rx) = channel();

    (0..10).for_each(move |i| {
        let tx = tx1.clone();
        spawn(move || { tx.send(Some(i)).unwrap(); });
    });

    for i in rx.iter() {
       println!("received {:?}", i);
    }
    println!("done!");
}
2 Likes

These solutions are definitely cleaner, but note they'll essentially end up doing the same thing (cloning 10 times and then dropping the original). If you need to avoid this for some reason, you can use itertools::repeat_n.

1 Like

It's prettier if you spawn tasks using rayon instead of managing threads the hard way.

BTW: if you use channels, use crossbeam-channel. It's faster and more flexible than std's implementation which is little more than Mutex<Vec>.

Thanks everyone, great / neat solutions there. I'll pick one to mark that thread resolved, but to be fair all of them look equally nice. :slight_smile:

@kornel While I know about rayon, in this particular case I wanted to do without.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.