4

How can you signal 'property' changes to multiple receivers in go?

Similar to how you would define a property in Qt with a notify signal.

E.g. if you imagine having some value that needs to be shown in multiple ways, like a progress value that could be shown both as a progress bar and as textual %, where both would need to update when the underlying value changes.

pjoe
  • 186
  • 1
  • 7

1 Answers1

5

One way could to be to utilize channels.

Your central code which manages/changes the property or variable that needs to be listened may provide a GetChan() function which returns a channel on which modifications (e.g. new values) will be broadcasted:

// The variable or property that is listened:
var i int

// Slice of all listeners
var listeners []chan int

func GetChan() chan int {
    listener := make(chan int, 5)
    listeners = append(listeners, listener)
    return listener
}

Whenever you change the variable/property, you need to broadcast the change:

func Set(newi int) {
    i = newi
    for _, ch := range listeners {
        ch <- i
    }
}

And listeners need to "listen" for change events, which can be done by a for range loop on the channel returned by GetChan():

func Background(name string, ch chan int, done chan int) {
    for v := range ch {
        fmt.Printf("[%s] value changed: %d\n", name, v)
    }
    done <- 0
}

Here is the main program:

func main() {
    l1 := GetChan()
    l2 := GetChan()

    done := make(chan int)

    go Background("B1", l1, done)
    go Background("B2", l2, done)

    Set(3)
    time.Sleep(time.Second) // Wait a little
    Set(5)

    // Close all listeners:
    for _, listener := range listeners {
        close(listener)
    }

    // Wait 2 background threads to finish:
    <-done
    <-done
}

And its output:

[B1] value changed: 3
[B2] value changed: 3
[B1] value changed: 5
[B2] value changed: 5

You can try the complete program on the Go Playground.

You may also implement a "broker" which realizes a subscriber model and allows broadcasting messages. See How to broadcast message using channel.

icza
  • 389,944
  • 63
  • 907
  • 827
  • Thanks, was thinking along those lines, but wondering if there was some standard or idiomatic way to do this. FWIW I played around some more and made an encapsulating PropertyInt type and added a method to also remove listeners, though I suspect adding/removing listeners would need locking to be fully threadsafe: http://play.golang.org/p/41ygGZ-QaB – pjoe Apr 07 '15 at 12:28
  • @pjoe Yes, the next step would be to do just that: create a `PropInt` type with methods just as you did. I excluded it to show only the principle. Locking would also be neccessary as you mentioned, that's why in my example I "pre-created" the listeners before started to modify the property. – icza Apr 07 '15 at 12:31
  • 3
    One issue glossed over here is blocking channels. The above makes the channels with a length of five but if a receiver goes away it will fill up and block `Set`. One strategy to avoid this is have the client provide the channel (with whatever buffering is appropriate to them, probably 0 or 1) and have the service do non-blocking sends skipping any full/un-ready channels (dropping the event). – Dave C Apr 07 '15 at 17:33