| <codewalk title="Share Memory By Communicating"> |
| |
| <step title="Introduction" src="doc/codewalk/urlpoll.go"> |
| Go's approach to concurrency differs from the traditional use of |
| threads and shared memory. Philosophically, it can be summarized: |
| <br/><br/> |
| <i>Don't communicate by sharing memory; share memory by communicating.</i> |
| <br/><br/> |
| Channels allow you to pass references to data structures between goroutines. |
| If you consider this as passing around ownership of the data (the ability to |
| read and write it), they become a powerful and expressive synchronization |
| mechanism. |
| <br/><br/> |
| In this codewalk we will look at a simple program that polls a list of |
| URLs, checking their HTTP response codes and periodically printing their state. |
| </step> |
| |
| <step title="State type" src="doc/codewalk/urlpoll.go:/State/,/}/"> |
| The State type represents the state of a URL. |
| <br/><br/> |
| The Pollers send State values to the StateMonitor, |
| which maintains a map of the current state of each URL. |
| </step> |
| |
| <step title="Resource type" src="doc/codewalk/urlpoll.go:/Resource/,/}/"> |
| A Resource represents the state of a URL to be polled: the URL itself |
| and the number of errors encountered since the last successful poll. |
| <br/><br/> |
| When the program starts, it allocates one Resource for each URL. |
| The main goroutine and the Poller goroutines send the Resources to |
| each other on channels. |
| </step> |
| |
| <step title="Poller function" src="doc/codewalk/urlpoll.go:/func Poller/,/\n}/"> |
| Each Poller receives Resource pointers from an input channel. |
| In this program, the convention is that sending a Resource pointer on |
| a channel passes ownership of the underlying data from the sender |
| to the receiver. Because of this convention, we know that |
| no two goroutines will access this Resource at the same time. |
| This means we don't have to worry about locking to prevent concurrent |
| access to these data structures. |
| <br/><br/> |
| The Poller processes the Resource by calling its Poll method. |
| <br/><br/> |
| It sends a State value to the status channel, to inform the StateMonitor |
| of the result of the Poll. |
| <br/><br/> |
| Finally, it sends the Resource pointer to the out channel. This can be |
| interpreted as the Poller saying "I'm done with this Resource" and |
| returning ownership of it to the main goroutine. |
| <br/><br/> |
| Several goroutines run Pollers, processing Resources in parallel. |
| </step> |
| |
| <step title="The Poll method" src="doc/codewalk/urlpoll.go:/Poll executes/,/\n}/"> |
| The Poll method (of the Resource type) performs an HTTP HEAD request |
| for the Resource's URL and returns the HTTP response's status code. |
| If an error occurs, Poll logs the message to standard error and returns the |
| error string instead. |
| </step> |
| |
| <step title="main function" src="doc/codewalk/urlpoll.go:/func main/,/\n}/"> |
| The main function starts the Poller and StateMonitor goroutines |
| and then loops passing completed Resources back to the pending |
| channel after appropriate delays. |
| </step> |
| |
| <step title="Creating channels" src="doc/codewalk/urlpoll.go:/Create our/,/complete/"> |
| First, main makes two channels of *Resource, pending and complete. |
| <br/><br/> |
| Inside main, a new goroutine sends one Resource per URL to pending |
| and the main goroutine receives completed Resources from complete. |
| <br/><br/> |
| The pending and complete channels are passed to each of the Poller |
| goroutines, within which they are known as in and out. |
| </step> |
| |
| <step title="Initializing StateMonitor" src="doc/codewalk/urlpoll.go:/Launch the StateMonitor/,/statusInterval/"> |
| StateMonitor will initialize and launch a goroutine that stores the state |
| of each Resource. We will look at this function in detail later. |
| <br/><br/> |
| For now, the important thing to note is that it returns a channel of State, |
| which is saved as status and passed to the Poller goroutines. |
| </step> |
| |
| <step title="Launching Poller goroutines" src="doc/codewalk/urlpoll.go:/Launch some Poller/,/}/"> |
| Now that it has the necessary channels, main launches a number of |
| Poller goroutines, passing the channels as arguments. |
| The channels provide the means of communication between the main, Poller, and |
| StateMonitor goroutines. |
| </step> |
| |
| <step title="Send Resources to pending" src="doc/codewalk/urlpoll.go:/Send some Resources/,/}\(\)/"> |
| To add the initial work to the system, main starts a new goroutine |
| that allocates and sends one Resource per URL to pending. |
| <br/><br/> |
| The new goroutine is necessary because unbuffered channel sends and |
| receives are synchronous. That means these channel sends will block until |
| the Pollers are ready to read from pending. |
| <br/><br/> |
| Were these sends performed in the main goroutine with fewer Pollers than |
| channel sends, the program would reach a deadlock situation, because |
| main would not yet be receiving from complete. |
| <br/><br/> |
| Exercise for the reader: modify this part of the program to read a list of |
| URLs from a file. (You may want to move this goroutine into its own |
| named function.) |
| </step> |
| |
| <step title="Main Event Loop" src="doc/codewalk/urlpoll.go:/range complete/,/\n }/"> |
| When a Poller is done with a Resource, it sends it on the complete channel. |
| This loop receives those Resource pointers from complete. |
| For each received Resource, it starts a new goroutine calling |
| the Resource's Sleep method. Using a new goroutine for each |
| ensures that the sleeps can happen in parallel. |
| <br/><br/> |
| Note that any single Resource pointer may only be sent on either pending or |
| complete at any one time. This ensures that a Resource is either being |
| handled by a Poller goroutine or sleeping, but never both simultaneously. |
| In this way, we share our Resource data by communicating. |
| </step> |
| |
| <step title="The Sleep method" src="doc/codewalk/urlpoll.go:/Sleep/,/\n}/"> |
| Sleep calls time.Sleep to pause before sending the Resource to done. |
| The pause will either be of a fixed length (pollInterval) plus an |
| additional delay proportional to the number of sequential errors (r.errCount). |
| <br/><br/> |
| This is an example of a typical Go idiom: a function intended to run inside |
| a goroutine takes a channel, upon which it sends its return value |
| (or other indication of completed state). |
| </step> |
| |
| <step title="StateMonitor" src="doc/codewalk/urlpoll.go:/StateMonitor/,/\n}/"> |
| The StateMonitor receives State values on a channel and periodically |
| outputs the state of all Resources being polled by the program. |
| </step> |
| |
| <step title="The updates channel" src="doc/codewalk/urlpoll.go:/updates :=/"> |
| The variable updates is a channel of State, on which the Poller goroutines |
| send State values. |
| <br/><br/> |
| This channel is returned by the function. |
| </step> |
| |
| <step title="The urlStatus map" src="doc/codewalk/urlpoll.go:/urlStatus/"> |
| The variable urlStatus is a map of URLs to their most recent status. |
| </step> |
| |
| <step title="The Ticker object" src="doc/codewalk/urlpoll.go:/ticker/"> |
| A time.Ticker is an object that repeatedly sends a value on a channel at a |
| specified interval. |
| <br/><br/> |
| In this case, ticker triggers the printing of the current state to |
| standard output every updateInterval nanoseconds. |
| </step> |
| |
| <step title="The StateMonitor goroutine" src="doc/codewalk/urlpoll.go:/go func/,/}\(\)/"> |
| StateMonitor will loop forever, selecting on two channels: |
| ticker.C and update. The select statement blocks until one of its |
| communications is ready to proceed. |
| <br/><br/> |
| When StateMonitor receives a tick from ticker.C, it calls logState to |
| print the current state. When it receives a State update from updates, |
| it records the new status in the urlStatus map. |
| <br/><br/> |
| Notice that this goroutine owns the urlStatus data structure, |
| ensuring that it can only be accessed sequentially. |
| This prevents memory corruption issues that might arise from parallel reads |
| and/or writes to a shared map. |
| </step> |
| |
| <step title="Conclusion" src="doc/codewalk/urlpoll.go"> |
| In this codewalk we have explored a simple example of using Go's concurrency |
| primitives to share memory through commmunication. |
| <br/><br/> |
| This should provide a starting point from which to explore the ways in which |
| goroutines and channels can be used to write expressive and concise concurrent |
| programs. |
| </step> |
| |
| </codewalk> |