loading...

Golang: Combining Signals by OR, AND

hauxe profile image Hậu Xe ・2 min read

In Golang if we want to avoid goroutine leaks, usually we use a special done channel to signal to a specific goroutine that it needs to be closed. And if we work with many modules which have it’s own done channels then we may want to combine those signals into one signal in form of OR or AND logic

The OR combining signals

If any of the signals is received then emit the combined signal

The implementation below use a number of 2 is maximum channels to be combined, you can change to any number you want:

// OR function combines all signal from channels into a single channel with Or condition
func OR(channels ...<-chan interface{}) <-chan interface{} {
    switch len(channels) {
    case 0:
        return nil
    case 1:
        return channels[0]
    }
    orDone := make(chan interface{})
    go func() {
        defer close(orDone)
        switch len(channels) {
        case 2:
            select {
            case <-channels[0]:
            case <-channels[1]:
            }
        default:
            select {
            case <-channels[0]:
            case <-channels[1]:
            case <-channels[2]:
            case <-OR(append(channels[3:], orDone)...):
            }
        }
    }()
    return orDone
}

The first case of switch statement is check if length of channels is 0, we will block forever by returning a nil channel, this is correct behavior when there no signal at all

The second case of switch statement is if only one channel is passed into this function we will simply return it!

Then we create a orChan for combining all signals from channel, then fork a new goroutine for combine at most 2 channel into orChan

Notice that when we recursively call OR function, we pass orChan again to this function because of logic OR, we need to respect the orChan from previous combination

The AND combining signals

If all of the signals is received then emit the combined signal

The implementation use a very excited knowledge in Golang: in the select statement if right-hand side of a written channel is a receiving channel , the select statement will wait for all receiving channels to be complete before sending to the left-hand side channel

// AND function combines all signal from channels into a single channel with And condition
func AND(channels ...<-chan interface{}) <-chan interface{} {
    switch len(channels) {
    case 0:
        return nil
    case 1:
        return channels[0]
    }
    andDone := make(chan interface{})
    collector := make(chan interface{}, len(channels))
    go func() {
        defer close(andDone)
        switch len(channels) {
        case 2:
            select {
            case collector <- <-channels[0]:
            case collector <- <-channels[1]:
            }
        default:
            select {
            case collector <- <-channels[0]:
            case collector <- <-channels[1]:
            case collector <- <-channels[2]:
            case collector <- <-AND(channels[3:]...):
            }
        }
    }()
    return andDone
}

It’s very similar to the OR implementation version with 3 important different points:

  • We create a buffered collector channel for collecting signals from combining channels

  • We pass collector channel as left-hand side in select statement

  • We recursively call AND function without pass in andChan into it’s parameter list, because we’re waiting for all signals, there no need to respect to this signal again

I implemented those function into this Repo, feel free to use it by yourself! Thanks

Posted on by:

hauxe profile

Hậu Xe

@hauxe

Senior Golang Developer

Discussion

pic
Editor guide