From Ruby to Go: Learning a World of Concurrency

Let me start by saying, I love Ruby. Ruby has been great to me. She was nice and easy on me when I began learning to program, understanding when I arbitrarily wanted to change a string variable to an integer, and has been a great companion as I’ve grown into deeper programming topics. I love you Ruby… but you love GIL… and we’re just never going to be threaded together.

All jokes aside, Ruby isn’t entirely useless in my want to experiment in concurrency, I could switch to JRuby (doesn’t have a GIL) and could be on my way to writing multithreaded Ruby applications. But why do that? Why not play in a language like Go? …I’m sure there are great reasons but I guess I just don’t want to be a one trick pony and Go makes a world of concurrency and parallelism easy(er) to dive into. I’ve wanted to scratch the itch of writing concurrent programs for a long time and now that come July I’ll be working in Go full time, I figured I should scratch away!

This is what I’ve learned in scratching that itch:

  1. Concurrency is NOT Parallelism:
“If you looked at the programming languages of today you’d probably get the image the the world is object oriented. It’s not. It is actually parallel.” ~ Rob Pike

I think what Rob meant by this is that the world is not composed of individual objects interacting with each other on a single timeline. There are various objects, yes, but all interacting, starting, ending, independent of one another. Sometimes they interact, sometimes not, they are all composed in such a way to be both independent and together. For example, you reading this article. Right now you and this website share something in common, but eventually (hopefully) you’re going to leave this site and go on your way. You and this website will continue on your own timelines maybe to interact and share some data again but for the most part, autonomous. A programming language (something meant to model the world) should be able to this also.

So what is concurrency? Well you could look it up, or just maybe be satisfied with the idea that it is the composition of independently executing things. Things could be programs, functions, methods, etc.

Wait, than what the hell is parallelism? Isn’t that the same thing? Well… no. Parallelism is the simultaneous execution of things, these things could be related, or not. I think Pike puts it best at a high level when he says:

“Concurrency is about dealing with a lot of things at once and parallelism is about doing a lot of things at once.”

I’m likely not going to be able to explain this as well as Rob Pike does with some great slides and props so I recommend you watch his talk here. The video is already queued to the portion where he expertly breaks the topic down in about 4 minutes. It is worth the 4 minutes of your life to watch, promise.

2. Go Routines:

In Go, a go routine (similar to a thread) is a concurrently executing activity. For example, let’s say you have a series of functions which all say hello in different languages. In a sequential program, you would run the program and each function would execute, the program would wait for each function to finish, and then execute the next. Something like:

helloEnglish()
helloSpanish()
helloFrench()
helloGerman()
helloChinese()
Hello!
Hola!
Bonjur!
Hallo!
你好

No matter how many times you run the program you would always get the same order of greetings, thus sequential execution. Now what if we concurrently execute our various hello functions? We should see that we will get varying order, depending on which routine finishes first, second, etc. Take a look:

Mind you, this is a pretty trivial example yet one which demonstrates the point. You might be wondering why we have the sleep at the end and that is because the main() function in Go is itself a go routine so if we don’t wait for the hello functions to return something, the main() will continue until exit because remember, none of our hello functions block! Also note how simple it is to make a routine, just add ‘go’ before the function call and whammo! …you’re running concurrent programs.

3. Channels:

Channels give us a way to help organize and communicate between go routines. Think of a channel as a pipe by which the information of go routines can flow. Here is a really basic example of a channel.

func main() {
ch := make(chan string) // make channel of strings
go pingEveryTwo(ch) // concurrently execute function
for { // infinite for loop
select { // select waits for responses
case res := <- ch: // receive "ping" from channel
fmt.Println(res) // print variable res
}
}
}
func pingEveryTwo(c chan string) {
for { // infitie loop
c <- “ping” // send "ping" to channel
time.Sleep(2 * time.Second) // sleep two seconds
}
}

Essentially by using a channel and a select statement you can accomplish a number of useful concurrency patters which will allow you to take advantage of running things in parallel and compose concurrent programs. This and this are both great resources about the different ways in which you can design various concurrency patterns in Go, I found them very useful in beginning to understand the topic.

If you’re into this and you’re especially keen, you might be thinking, “What if two go routines try to push to the same channel at the same time?” This could absolutely happen and likely will if you have a plethora of routines all trying to communicate into the same channel. The good news? Unbuffered channels (here is more on buffered channels) will block on both their send and receive operations. Thus, communication over an unbuffered channel will cause go routines to synchronize. Example:

func main() {
ch := make(chan string) // make channel of strings
go pingEveryTwo(ch) // concurrently execute function
go pongEveryOne(ch) // concurrently execute function
go waitStatus(ch) // concurrently execute function
for { // infinite for loop
select { // select waits for responses
case res := <- ch: // receive "ping" from channel
fmt.Println(res) // print variable res
}
}
}
func pingEveryTwo(c chan string) {
for { // infitie loop
c <- “ping” // send "ping" to channel
time.Sleep(2 * time.Second) // sleep two seconds
}
}
func pongEveryOne(c chan string) {
for { // infitie loop
c <- “pong” // send "ping" to channel
time.Sleep(1 * time.Second) // sleep two seconds
}
}
func waitStatus(c chan string) {
for { // infinite loop
c <- "waiting..." // send string to chan
time.Sleep(500 * time.Millisecond) // wait 500ms
}
}

As you can see, the channel synchronizes the three go routines ping, pong, and wait with the main go routine. How fantastic! But alas, you have no guarantee when the three all “finish” at the “same time” which one is actually going to come first because remember they are all non-blocking (until they send to the channel which blocks). This is why every two seconds you may see, ping then pong -or- you may see pong then ping.

4. Why Does This Even Matter?

This matters because our world is becoming increasingly more complex, data is becoming bigger, memory is becoming cheaper, and over all we humans expect more and more from technology. Or, maybe it matters because we want to take full advantage of what a computer is capable of. Or… maybe it matters because we once wanted to solve this problem and now we want to solve this problem. Whatever the reason, we are getting more powerful computers for cheaper and we should be writing programs which take advantage of that fact so we can all own things like this:

Go let’s us dive into concurrency quickly and easily, as it is baked into the language, without keeping permanent training wheels on. What I mean by that is, Go might make this appear simple, because it does make it simple, but it does not do so in a way that limits the language. If you have not explored Go, I recommend giving it a shot and playing around… it’ll be the most fun you’ve had programming in a while, promise.

Follow me: @lowellmower on twitter and medium

Like what you read? Give lowell mower a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.