123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- <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 communication.
- <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>
|