GithubHelp home page GithubHelp logo

Broadcast to all consumers about chan HOT 4 CLOSED

burntsushi avatar burntsushi commented on July 19, 2024
Broadcast to all consumers

from chan.

Comments (4)

BurntSushi avatar BurntSushi commented on July 19, 2024

No, and I'm not inclined to add such a feature. I think it would be more appropriate to build it on top of the existing API using existing channels as building blocks. I'm on mobile right now, but let me know if you want me to explain more.

from chan.

euclio avatar euclio commented on July 19, 2024

Sure, if you don't mind.

from chan.

BurntSushi avatar BurntSushi commented on July 19, 2024

OK, so one of the main problems with providing a "broadcast feature" is that there are many different ways to do it. Does the producer block until all consumers have received a value? Do all available consumers get sent a value or do only consumers who are ready to receive a value get sent a value? Merging this type of API with the existing Sender/Receiver types complicates things even more. In particular, in order to broadcast a value, it must be copyable, which is a restriction that isn't needed for simple ownership transfer like what the existing channels use.

My first recommendation therefore is explore the specific problem you're trying to solve. What semantics do you need? A really simple broadcaster is pretty easy to write. Here's one:

extern crate chan;

use std::sync::RwLock;
use std::thread;

pub struct Broadcaster<T> {
    registry: RwLock<Vec<chan::Sender<T>>>,
}

impl<T: Clone> Broadcaster<T> {
    pub fn new() -> Self {
        Broadcaster { registry: RwLock::new(vec![]) }
    }

    /// Create a new listener of broadcasted values.
    ///
    /// Every listener created by this method will receive all values sent by
    /// the `broadcast` method.
    pub fn listener(&self) -> chan::Receiver<T> {
        let (send, recv) = chan::sync(0);
        let mut registry = self.registry.write().unwrap();
        registry.push(send);
        recv
    }

    /// Broadcast `value` to all listeners.
    ///
    /// This call blocks until all listeners have received a copy of `value`.
    pub fn broadcast(&self, value: T) {
        let registry = self.registry.read().unwrap();
        for sender in &*registry {
            sender.send(value.clone());
        }
    }
}

fn main() {
    let broadcaster = Broadcaster::new();
    let mut handles = vec![];
    for i in 0..10 {
        let listener = broadcaster.listener();
        handles.push(thread::spawn(move || {
            println!("[thread {}] received: {}", i, listener.recv().unwrap());
        }));
    }
    broadcaster.broadcast(42);
    for h in handles {
        h.join().unwrap();
    }
}

This broadcaster will block until all listeners have received the sent value.

There are lots of variations available here. For example, the above code lets you work with chan::Receiver, which means the chan_select! macro is still useful. But it does not let you work with chan::Sender, since you must send values using the special broadcast method. Another approach could change that. For example:

#[macro_use]
extern crate chan;

use std::thread;

pub struct Broadcaster<T> {
    writer: chan::Sender<T>,
    register: chan::Sender<chan::Sender<T>>,
}

impl<T: Clone + Send + 'static> Broadcaster<T> {
    pub fn new() -> Self {
        let (writer_tx, writer_rx) = chan::sync::<T>(0);
        let (register_tx, register_rx) = chan::sync(0);
        thread::spawn(move || {
            let mut registry: Vec<chan::Sender<T>> = vec![];
            loop {
                chan_select! {
                    register_rx.recv() -> sender => {
                        match sender {
                            None => break,
                            Some(sender) => registry.push(sender),
                        }
                    },
                    writer_rx.recv() -> value => {
                        match value {
                            None => break,
                            Some(value) => {
                                for sender in &registry {
                                    sender.send(value.clone());
                                }
                            }
                        }
                    },
                }
            }
        });
        Broadcaster {
            writer: writer_tx,
            register: register_tx,
        }
    }

    pub fn channel(&self) -> (chan::Sender<T>, chan::Receiver<T>) {
        let (send, recv) = chan::sync(0);
        self.register.send(send);
        (self.writer.clone(), recv)
    }
}

fn main() {
    let broadcaster = Broadcaster::new();
    let mut handles = vec![];
    for i in 0..10 {
        let (_, listener) = broadcaster.channel();
        handles.push(thread::spawn(move || {
            println!("[thread {}] received: {}", i, listener.recv().unwrap());
        }));
    }
    broadcaster.channel().0.send(42);
    for h in handles {
        h.join().unwrap();
    }
}

This one uses a thread for each broadcaster and handles synchronization between channels. Notice that the channel method returns the ends of two different channels. Another difference is that sends no longer block on all receivers having received a value. (Semantically, the sender has a buffer of size 1, since subsequent sends cannot begin until all receivers have received a value.) Other valid semantics include using non-blocking sends to receivers.

A major downside (IMO) of the above two approaches is that the Receiver values returned by listener and channel are still independently cloneable. Those clones will not have broadcast semantics. (Which could actually be desirable! It is yet another variation.)

I think the key point I'm trying to convey is that since there are so many ways to do it, we have two realistic options:

  1. Don't include it. Let people devise their own solutions depending on their specific problem.
  2. Spend a bunch of time trying to craft an API that covers most use cases. I think it will be hard to make this API easy to understand.

I'm more inclined toward (1). If there is a simple solution to (2), then I might be willing to accept a PR.

from chan.

euclio avatar euclio commented on July 19, 2024

Thanks for the writeup! I see your reasoning. Closing this in favor of writing my own broadcaster.

from chan.

Related Issues (19)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.