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
Top comments (0)