| <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> |