Writing Your First Concurrent Go Program on Ubuntu

Google’s Go programming language has been around since 2009 and in 2012, the language reached its official v1.0 status. In those intervening years, much has changed including how the the language is installed. Back in its infancy, there weren’t any official binary distributions and you either had to build Go from the source code, which was the recommended method since the language was changing frequently, or use a pre-built package for your Linux distribution. Back then Windows support was limited as was support for CPU architectures other than Intel.

Things have improved vastly since then. For Linux, there are two easy ways to install Go. Download the official Linux binary build from the Go downloads page or opt for a pre-built package for your Linux distribution. The easiest way to install Go on Ubuntu is to use apt-get:

sudo apt-get install golang

Once Go is installed, you can start to develop programs. One of the simplest Go programs is the classic “Hello World!” program. Using a text editor, create a file called “hellomte.go” with the following short Go code:

package main
 
import "fmt"
 
func main() {
     fmt.Println("Hello Make Tech Easier!")
}

Since v1.0 of Go, the need for individual compile and link commands has been removed and the old 8g and 8l commands have been replaced with the go command.

To run hellomte.go, open a terminal and change directory to the folder which contains the source code file, then type:

go run hellomte.go

This will compile and run the Go program but it won’t produce an executable binary. To create a binary and then run it use the go build command:

go build hellomte.go
./hellomte

One of the defining features of the Go programming language is its support for concurrency which allows a program to work with multiple tasks at once. Parallelism, which is similar to concurrency, allows a program to execute lots of tasks simultaneously, but concurrency goes one step further in that it allows these separate tasks to communicate and interact. As a result, Go allows programmers to use a whole variety of different concurrent designs including worker pools, pipelines (where one task happens after another), and synchronous or asynchronous background tasks. The foundation of this concurrency is the goroutine coupled with channels and Go’s select statement.

Here is a simple Go program which prints out a string several times using a concurrent goroutine:

package main
 
import (
    "fmt"
    "time"
)
 
func say(s string) {
    for i := 0; i < 5; i++ {
        fmt.Println(s)
    }
}
 
func main() {
    go say("Hello Make Tech Easier!")
    fmt.Println("Sleep a little...")
    time.Sleep(100 * time.Millisecond)
}

The function say() just performs a simple loop to print out the string (parameter s) five times. The interesting thing is how that function is called. Rather than just calling say("Hello Make Tech Easier!") the keyword go is placed in front of the function call. This means that the function will be run as a separate task. The rest of the main() function then just sleeps a little to give time for the goroutine to complete.

The output may surprise you:

go-lang-concurrent

As you can see the say() function is run as a goroutine and while it is being setup the rest of the main() function continues, printing out Sleep a little... and then going to sleep. By then the goroutine is active and starts to print out the string five times. Finally the program ends once the timeout limit as been reached.

goroutines can communicate using channels. A channel opens up a line of communication between two different parts of a Go program. Typically a function will be called as a goroutine and if it needs to send back data (say from a network operation) it can use a channel to pass along that data. If another part of the Go program is waiting for that data, it will sleep until the data is ready. Channels are created using the make() function and they can be passed as parameters to goroutines.

Consider this code:

package main
 
import (
"fmt"
)
 
func say(s string, c chan int) {
     var i int
     for i = 0; i < 5; i++ {
          fmt.Println(s)
     }
     c <- i
}
 
func main() {
     c := make(chan int)
     go say("Hello Make Tech Easier!", c)
     sts := <- c
     fmt.Println(sts)
}

The say() function is very similar to the first example with the exception that the second parameter is a channel and that after printing out the string, the number of iterations will be send down the channel via the c <- i line of code.

The main function creates a channel, starts the say() function as a goroutine and then waits for the data to come down the channel, sts := <- c before printing the result.

The Go language has progressed significantly over the last few years, if you haven’t looked at it recently the perhaps now is a good time!