Today’s post isn’t exactly automation-related, but I’ve been having a lot of fun learning Golang over the last week or two and felt the need to share some of the things that I really like about the language and what I think it’s strengths are.

Why Golang?

I first heard of Golang about five or six years ago, around 2011-2012. At that point in my career, I was working on Siemens’ industrial CAD system as a fairly junior developer, coding in C++. The product I was working on was quite old and a number of my colleagues had been working on it since the 1980s.

It was a code base that has lived through many an era in software development and I took the opportunity to read some of the classics, like K&R (still the authoritative guide to C, despite its age) to better myself. My first proper programming language as a teenager was C, but before taking that job I only had industrial experience of Java. It felt like traveling back in time to go forward, but it was a positive experience. I was actually going over K&R around the time that Dennis Ritchie, one of the authors, died in 2011.

One of my mentors around that time discussed playing around with Golang and I discovered that several of the old masters from the Bell Labs era were involved in it. Brian Kernighan, the other author of K&R, even co-wrote The Go Programming Language, a clear nod to the classic text.

My recent experiments with Docker had got me thinking what language might be best for writing containerized microservices. “Best language” is always an over-simplification, all languages have their use cases and there’s nothing about containers that make those use cases redundant, but what I was particularly concerned with was raw performance versus size, for container density.

What I discovered was that Golang is fast. It’s (mostly) statically compiled into machine code, so there’s no JVM or CLR required to run it. It’s designed from the ground up for multi-core concurrency and has performance comparable to C++ without the language complexity or lack of memory management. Thanks to it producing native executables, it can run in the scratch Docker container without a full Linux distro, meaning you can cram a service into a ~20MB Docker container. How’s that for density?

A simple Golang web service and client

Code available at https://github.com/kmacphee/golang-samples/tree/master/websockets/pingpong

For one of my first out-of-book exercises, I decided to write a simple client and server that communicated over a WebSocket, mainly for use in further Docker experiments. They just pass the text messages “ping” and “pong” to each other every five seconds. If you have no prior experience of Golang the server is the easier of the two to understand, so we will start there.

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
    "time"

    "github.com/gorilla/websocket"
)

func main() {
    http.HandleFunc("/pingpong", handler)
    http.ListenAndServe(":80", nil)
}

var upgrader = websocket.Upgrader {
    ReadBufferSize: 1024,
    WriteBufferSize: 1024,
}

func handler(res http.ResponseWriter, req *http.Request) {
    // Create log file and logger.
    logFile, err := os.Create("pong.log")
    if err != nil {
        fmt.Println("Failed to create pong.log")
        return
    }
    defer logFile.Close()
    log := log.New(logFile, "", log.Lmicroseconds)

    // Perform WebSocket upgrade.
    conn, err := upgrader.Upgrade(res, req, nil)
    if err != nil {
        log.Println("Upgrade Error: ", err)
        return
    }
    defer conn.Close()

    log.Println("WebSocket connection initiated.")

    for {
        msgType, bytes, err := conn.ReadMessage()
        if err != nil {
            log.Println("Read Error: ", err)
            break
        }

        // We don't recognize any message that is not "ping".
        if msg := string(bytes[:]); msgType != websocket.TextMessage && msg != "ping" {
            log.Println("Unrecognized message received.")
            continue
        } else {
            log.Println("Received: ping.")
        }

        time.Sleep(5 * time.Second)
        log.Println("Sending: pong.")
        err = conn.WriteMessage(websocket.TextMessage, []byte("pong"))
        if err != nil {
            log.Println("Write Error: ", err)
            break
        }
    }

    log.Println("WebSocket connection terminated.")
}

Note: This example uses the Gorilla web toolkit’s implementation of WebSocket as it’s more full-featured than the standard library equivalent, by its own admission in the docs. The WebSocket sample code provided in their GitHub repo was a great learning tool for me and helped me learn a lot of what is demonstrated in this blog post. So I highly recommend them.

This is fairly easy to follow:

  • The handler function is responsible for handling a HTTP request sent to /pingpong. It gets assigned to that URI in main.
  • handler performs an upgrade to establish a WebSocket connection with the client.
  • From then on, handler responds to any “ping” message from the client with “pong”, after a five-second wait.

Some interesting language features demonstrated here are:

  • Multiple return values (line 44): Functions can return multiple values. Rather than have try-catch blocks everywhere, errors are often returned as one of multiple return values and checked for not nil.
  • Deferred functions (line 30): Functions can have their execution deferred to when the current function scope terminates. This call to logFile.Close() ensures that the file closes cleanly, immediately after handler terminates. Here it is used somewhat like a finally block is used in other languages, but there are other uses that make it a very powerful mechanism, which will be described below.
  • Inferred static types (line 31): Golang is statically typed, meaning no runtime interpretation of types and no type coercion without an explicit cast (we’ve all been stung my implicit type coercion at some point, haven’t we?). It can infer the type of a variable when it is initialized though, somewhat like how var is used rather than an explicit type in other languages. Go has var (see line 18) and explicit type declarations also, but the := operator can be used as a concise alternative for initialization and inferring the type.
  • No while loop (line 43): Golang has no while loop. A for loop without an initializer and a post-loop increment can be used exactly like a while loop in other languages. A for loop without a clause, like shown, is an infinite loop.
  • If statements with initializer (line 51): In many languages, we often initialize var i = 0 as the first clause of a for loop. In Golang, if statements can have an initialization statement separated from the boolean test with a semi-colon, as for loops do.

Now for the client:

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"net/url"
	"time"

	"github.com/gorilla/websocket"
)

var server = flag.String("server", "localhost:80", "server address")

func main() {
    flag.Parse()

    interrupt := make(chan os.Signal, 1)
    signal.Notify(interrupt, os.Interrupt)

    // Create log file and logger.
    logFile, err := os.Create("ping.log")
    if err != nil {
        fmt.Println("Failed to create ping.log")
        return
    }
    defer logFile.Close()
    log := log.New(logFile, "", log.Lmicroseconds)

    url := url.URL {
        Scheme: "ws",
        Host: *server,
        Path: "/pingpong",
    }
    log.Printf("Making connection to: %s", url.String())

    conn, _, err := websocket.DefaultDialer.Dial(url.String(), nil)
    if err != nil {
        log.Fatal("Dial Error: ", err)
    }
    defer conn.Close()

    done := make(chan struct{})

    // This Goroutine is our read/write loop. It keeps going until it cannot use the WebSocket anymore.
    go func() {
        defer conn.Close()
        defer close(done)

        for {
            log.Println("Sending: ping.")
            err = conn.WriteMessage(websocket.TextMessage, []byte("ping"))
            if err != nil {
                log.Println("Write Error: ", err)
                break
            }

            msgType, bytes, err := conn.ReadMessage()
            if err != nil {
                log.Println("WebSocket closed.")
                return
            }
            // We don't recognize any message that is not "pong".
            if msg := string(bytes[:]); msgType != websocket.TextMessage && msg != "pong" {
                log.Println("Unrecognized message received.")
                continue
            } else {
                log.Println("Received: pong.")
            }

            time.Sleep(5 * time.Second)
        }
    }()

    for {
        select {
        // Block until interrupted. Then send the close message to the server and wait for our other read/write Goroutine
        // to signal 'done'. This is how you safely terminate a WebSocket connection.
        case <-interrupt:
            log.Println("Client interrupted.")
            err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
            if err != nil {
                log.Println("WebSocket Close Error: ", err)
            }
            // Wait for 'done' or one second to pass.
            select {
            case <-done:
            case <-time.After(time.Second):
            }
            return
        // WebSocket has terminated before interrupt.
        case <-done:
            log.Println("WebSocket connection terminated.")
            return
        }
    }
}

There’s a little more going on here:

  • main sets up a channel (chan) for the interrupt signal from the operating system. If you don’t know what a channel is, we’ll cover it below.
  • It then creates a log file, assembles the URI from the server command line flag (which defaults to localhost if not supplied) and attempts to establish the WebSocket with the server located at the URI.
  • Then it sets up another channel (chan) called done.
  • Then it runs a Goroutine (a concurrent function run on another thread). The Goroutine is a closure, so has access to the done variable. It defers close(done) to trigger at the end of its scope, closing that channel is how it signals to the main function that it has finished. It also defers the close of the WebSocket connection.
  • main then enters a loop, which blocks on receiving something on the interrupt or the done channels.
  • If something is received on the interrupt channel. main triggers the safe closure of the WebSocket connection by sending websocket.CloseMessage to the server. This will cause the Goroutine‘s ReadMessage call to fail in the concurrent method and the Goroutine to return, triggering the deferred close on the done channel. main then waits for that done signal to be received or one second, whichever is sooner, before returning.
  • If the done channel receives a message (i.e. the Goroutine returns) for any other reason, main returns.

If you don’t know Golang, your head is possibly spinning right now. Don’t worry, read the following explanation of the language features in play and try to follow the code again.

  • Goroutines (line 46): Goroutines are the same as any other function in Golang, the only difference is that the keyword go tells the runtime to execute the method on a different thread, rather than on the current thread’s call stack. It really is that simple – one keyword.
  • Channels (lines 18 and 43): Channels are the second half of Golang’s concurrency magic. Channels are like sockets that different threads can use to pipe data between them in a thread-safe way. You can see a channel for an OS interrupt signal (line 18) and a channel that doesn’t transmit data at all (line 43), it just acts as an event notifier. I don’t think there has ever been a programming language primitive as powerful or expressive for concurrent programming as Golang channels.
  • Closures (line 46): Our Goroutine is also a closure, a function literal. As such it has access to main‘s local variables (outer scope). Thus, it can make use of the done channel, which is how the concurrent methods synchronize with each other when it is time to close the WebSocket connection down safely.
  • Select (lines 76 and 86): Selects look like switch statements, but they block on receiving a signal from multiple channels (the cases). Whichever of the channels it receives something on, that case statement is executed. In a for loop, it can do this continuously. With selects and channels you can build powerful event-driven logic.

Golang is rewiring my brain

Wrapping my head around channels and using them has taken some brain cycles. Even so, I’m absolutely amazed by the power of Golang. Once you “get it”, Golang is very simple. It’s making me think about solving problems in whole new ways, which for me is a sign that I have come across something truly wonderful. I’ve been completely entranced by it for the last two weeks.

If you’re interested in getting started with the language, I can recommend the interactive tutorial: A Tour of Go. Afterward, buy a good book with a practical focus. I’m currently working through Go in Practice, which is brilliantly written. After that, I’m looking forward to the journey to mastery with my old friend ‘K’ in The Go Programming Language.

About the Author Kirk MacPhee

An experienced software developer and technical lead, specializing in automation technologies and their application.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s