This article will cover 5 simple concurrency patterns which are often used in Golang
1. for-select
pattern
This is a fundamental pattern. It is typically used to read data from multiple channels.
var c1, c2 <-chan int for { // Either loop infinitely or range over something select { case <-c1: // Do some work with channels case <-c2: default: // auto run if other cases are not ready } // do some work }
The select statement looks like switch one, but its behavior is different. All cases
are considered simultaneously & have equal chance to be selected. If none of the cases
are ready to run, the entire select
statement blocks.
2. done
channel pattern
Goroutine is not garbage collected; hence, it is likely to be leaked.
go func() { // <operation that will block forever> // => Go routine leaks }() // Do work
To avoid leaking, Goroutine should be cancelled whenever it is told to do. A parent Goroutine needs to send cancellation signal to its child via a read-only channel named done
. By convention, it is set as the 1st parameter.
This pattern is also utilized a lot in other patterns.
//child goroutine doWork(<-done chan interface {}, other_params) <- terminated chan interface{} { terminated := make(chan interface{}) // to tell outer that it has finished defer close(terminated) for { select: { case: //do your work here case <- done: return } // do work here } return terminated } // parent goroutine done := make(chan interface{}) terminated := doWork(done, other_args) // do sth // then tell child to stop close (done) // wait for child finish its work <- terminated
3. or-channel
pattern
This pattern aims to combine multiple done
channels into one agg_done
; it means that if one of a done
channel is signaled, the whole agg_done
channel is also closed. Yet, we do not know number of done
channels during runtime in advanced.
or-channel
pattern can do so by using goroutine
& recursion
.
// return agg_done channel var or func(channels ... <-chan interface{}) <- chan interface{} or = func(channels ...<-chan interface{}) <-chan interface{} { // base cases 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)...): // * line } } } return orDone }
line * makes the upper & lower recursive function depends on each other like a tree. The upper injects its own orDone
channel into the lower. Then the lower also return its own orDone
to the upper.
If any orDone
channel closes, the upper & lower both are notified.
4. tee
channel pattern
This pattern aims to split values coming from a channel into 2 others. So that we can dispatch them into two separate areas of our codebase.
tee := func( done <- chan interface{}, in <- chan interface{}, ) (<- chan interface, <- chan interface) { out1 := make(chan interface{}) out2 := make(chan interface{}) go func() { defer close(out1) defer close(out2) //shadow outer variable var out1, out2 = out1, out2 for val := range orDone(done, in) { for i := 0; i < 2; i ++ { //make sure 2 channels received same value select { case <- done: case out1<- val: out1 = nil //stop this channel from being received case out2<-val: out2 = nil } } } }() return out1, out2 }
5. bridge
channel pattern
Reading values from channel of channels (<-chan <-chan interface{}
) can be cumbersome. Hence, this pattern aims to merge all values into 1 channel, so that the consumer jobs is much easier.
bridge := func( done <- chan interface{}, chanStream <- <- interface{}, ) <- chan interface{} { valStream := make(chan interface{}) go func() { defer close(valStream) for { var stream <- chan interface{} select { case maybeStream, ok := <-chanStream if ok == false { return } stream = maybeStream case <- done: return } for val := range orDone(done, stream){ select{ case valStream <- val: case <- done: } } } }() return valStream }
References:
Concurrency in Go: Tools and Techniques for Developers - Book by Katherine Cox-Buday
Top comments (0)