In the last post, the tutorial that Barton Paul Levenson wrote described a simple radiative model of the earth with no atmosphere. In this post, I will modify the code that I wrote for this model and show some of the power and expressiveness of Go (hopefully).
Before I begin with the code, I wanted to point out a video and a paper that I think shows some of the promise of Go. The video is of Rob Pike describing how channels and goroutines allows a programmer to write exactly what he wants to do, and no more.
The paper, mentioned by Pike in the video, is called "Squinting at Power Series". This paper describes how to use channels and concurrency to represent power series. This representation is inherent in the concurrency and allows various operators to be defined extremely quickly. In particular, defining differentiation and integration are almost trivial when representing power series in this manner.
One of the interesting aspects of this video and paper, the video was recorded as Rob Pike was just starting to work on Go (or maybe just before) and the paper was written several years before that.
Ok, now back to GoGCM. At the end of the last post, I wrote Model_001. This code had two functions,
func solar(ch chan int) (out chan datapoint) and
func gcm(ch chan datapoint) (out chan datapoint)
Each of these functions started a goroutine, which communicated to the main program through two channels. The input (passed as ch in the parameters) and the output channel (created by the function and returned to the caller). All these channels pass information by value, meaning a copy is made to give to the function or return from the function. This is fine for this small toy example (where there are 4 values in a struct), however as the examples will become more and more involved, we will want to be able to store information in one location and refer a function to that location in order to reduce the overhead.
I'm going to introduce you to a channel of pointers. To understand this, you must first understand what channels are and how they behave. A much better place to learn about channels is the Go resource sites linked on the side of this blog, but I will try to explain some of the basics. If you have any questions, don't hesitate to ask them in the comments.
A channel is a way to pass information from one goroutine (lightweight thread of execution) to the next. A channel is like any other variable, you can assign it, allocate it, but the major difference is that you can also send or receive from it. If I create a channel like so
ch := make(chan int)
then I send a value to it
ch <- int(1)
another goroutine can receive that value from the same channel like this
in := <-ch
fmt.Printf(in) // Prints 1
Channels can pass more than just values however, and can pass user created structures as well such as datapoint in model_001, pointers or even other channels (channel of channels!). What we want to do is pass pointers to the datapoint structure, since this is where all the data of our model is located. The functions are now redefined like so
func solar(ch chan *datapoint) (out chan *datapoint) and
func gcm(ch chan *datapoint) (out chan *datapoint)
Now I hear some are already groaning, because passing pointers between executing concurrent code is dangerous without mutexes, semaphores and all kinds of concurrency contructs and an expert who can code all of the above with a sufficient artistic touch! Using idiomatic Go, it's just not necessary! Go has a very important slogan that describes how idiomatic Go deals with these issues. (see the Effective Go page on the website www.golang.org).
"Do not communicate by sharing memory; instead, share memory by communicating"
The go blog describes this in the post "Share Memory by Communicating". The principle is that when you pass a pointer over a channel, the receiving goroutine becomes the owner of that resource. This lets you inspect your code quickly and see when you are the owner and when it's owned by another goroutine.
I have implemented this idea in model_001, so that a single datapoint variable is passed to func solar(...) and to func gcm(...). This structure of programming is very versatile. To demonstrate this, lets say that we know what the temperature was on our model world, and we also know that the only way it varied was by the variation of the solar output. By adding a few extra lines of code in each function, we can pass a blank datapoint to gcm, which calculates the temperature and the required F. This datapoint is then passed to solar, which calculates the solar constant required (S).
In fact, you can even pass datapoint to solar or gcm in any order (even randomly). I have coded a solar driven and a temperature driven function, and even demonstrated a hybrid driven model.
The power of goroutines to express how to assemble a model is one of the great strengths of Go. I hope this examples demonstrates this. Let me know what you think, or if you see a way to improve this example.
No comments:
Post a Comment