2010-06-30 00:56:30 -06:00
|
|
|
<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>
|
|
|
|
|
2012-03-23 15:12:52 -06:00
|
|
|
<step title="Creating channels" src="doc/codewalk/urlpoll.go:/Create our/,/complete/">
|
2010-06-30 00:56:30 -06:00
|
|
|
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>
|
|
|
|
|
2012-03-23 15:12:52 -06:00
|
|
|
<step title="Initializing StateMonitor" src="doc/codewalk/urlpoll.go:/Launch the StateMonitor/,/statusInterval/">
|
2010-06-30 00:56:30 -06:00
|
|
|
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>
|
|
|
|
|
2012-03-23 15:12:52 -06:00
|
|
|
<step title="Launching Poller goroutines" src="doc/codewalk/urlpoll.go:/Launch some Poller/,/}/">
|
2010-06-30 00:56:30 -06:00
|
|
|
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>
|
|
|
|
|
2012-03-23 15:12:52 -06:00
|
|
|
<step title="Send Resources to pending" src="doc/codewalk/urlpoll.go:/Send some Resources/,/}\(\)/">
|
2010-06-30 00:56:30 -06:00
|
|
|
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>
|