diff options
Diffstat (limited to 'content/concurrency-timeouts.article')
-rw-r--r-- | content/concurrency-timeouts.article | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/content/concurrency-timeouts.article b/content/concurrency-timeouts.article new file mode 100644 index 0000000..8237e7d --- /dev/null +++ b/content/concurrency-timeouts.article @@ -0,0 +1,84 @@ +# Go Concurrency Patterns: Timing out, moving on +23 Sep 2010 +Tags: concurrency, technical +Summary: How to implement timeouts using Go's concurrency support. +OldURL: /go-concurrency-patterns-timing-out-and + +Andrew Gerrand + +## + +Concurrent programming has its own idioms. +A good example is timeouts. Although Go's channels do not support them directly, +they are easy to implement. +Say we want to receive from the channel `ch`, +but want to wait at most one second for the value to arrive. +We would start by creating a signalling channel and launching a goroutine +that sleeps before sending on the channel: + + timeout := make(chan bool, 1) + go func() { + time.Sleep(1 * time.Second) + timeout <- true + }() + +We can then use a `select` statement to receive from either `ch` or `timeout`. +If nothing arrives on `ch` after one second, +the timeout case is selected and the attempt to read from ch is abandoned. + + select { + case <-ch: + // a read from ch has occurred + case <-timeout: + // the read from ch has timed out + } + +The `timeout` channel is buffered with space for 1 value, +allowing the timeout goroutine to send to the channel and then exit. +The goroutine doesn't know (or care) whether the value is received. +This means the goroutine won't hang around forever if the `ch` receive happens +before the timeout is reached. +The `timeout` channel will eventually be deallocated by the garbage collector. + +(In this example we used `time.Sleep` to demonstrate the mechanics of goroutines and channels. +In real programs you should use ` [time.After](https://golang.org/pkg/time/#After)`, +a function that returns a channel and sends on that channel after the specified duration.) + +Let's look at another variation of this pattern. +In this example we have a program that reads from multiple replicated databases simultaneously. +The program needs only one of the answers, +and it should accept the answer that arrives first. + +The function `Query` takes a slice of database connections and a `query` string. +It queries each of the databases in parallel and returns the first response it receives: + + func Query(conns []Conn, query string) Result { + ch := make(chan Result) + for _, conn := range conns { + go func(c Conn) { + select { + case ch <- c.DoQuery(query): + default: + } + }(conn) + } + return <-ch + } + +In this example, the closure does a non-blocking send, +which it achieves by using the send operation in `select` statement with a `default` case. +If the send cannot go through immediately the default case will be selected. +Making the send non-blocking guarantees that none of the goroutines launched +in the loop will hang around. +However, if the result arrives before the main function has made it to the receive, +the send could fail since no one is ready. + +This problem is a textbook example of what is known as a [race condition](https://en.wikipedia.org/wiki/Race_condition), +but the fix is trivial. +We just make sure to buffer the channel `ch` (by adding the buffer length +as the second argument to [make](https://golang.org/pkg/builtin/#make)), +guaranteeing that the first send has a place to put the value. +This ensures the send will always succeed, +and the first value to arrive will be retrieved regardless of the order of execution. + +These two examples demonstrate the simplicity with which Go can express complex interactions between goroutines. |