Multi-threaded applications are very complicated, especially when your code is not organized and consistent with how resources are accessed, managed and maintained. If you want to minimize bugs you need philosophies and rules to live by. Here are some of mine:

  1. Resource allocation and de-allocation should be abstracted and managed within the same type.
  2. Resource thread safeness should be abstracted and managed within the same type.
  3. A public interface should be the only means to accessing shared resources.
  4. Any thread that allocates a resource should de-allocated the same resource.
In Go we don't have threads but Go Routines. The Go runtime abstracts the threading and task swapping of these routines. Regardless, the same philosophies and rules apply.

One of my favorite design patterns is the Singleton. It provides a great implementation when you only need one instance of a type and that type manages shared resources. A Singleton is a design pattern where the type creates an instance of itself and keeps that reference private. Access to the shared resources managed by that reference is abstracted through a static pubic interface. These static methods also provide thread safeness. The application using the Singleton is responsible for initializing and de-initializing the Singleton but never has direct access to the internals.

It escaped me for some time how to implement a Singleton in Go because Go is not a traditional object oriented programming language and there are no static methods.

I consider Go to be a light object oriented programming language. Yes it does have encapsulation and type member functions but it lacks inheritance and therefore traditional polymorphism. In all of the OOP languages I have ever used, I never used inheritance unless I wanted to implement polymorphism. With the way interfaces are implemented in Go there is no need for inheritance. Go took the best parts of OOP, left out the rest and gave us a better way to write polymorphic code.

In Go we can implement a Singleton by leveraging the scoping and encapsulation rules of packages and types. For this post we will explore my straps package since it will give us a real world example.

The straps package provides a mechanism to store configuration options (straps) in an XML document and read them into memory for use by the application. The name strap comes from the early days of configuring networking equipment. Those settings were called straps and that name has always stuck with me. In the MacOS we have .plist files, in .Net we have app.config files and in Go I have straps.xml files.

Here is a sample straps files for one of my applications:

<straps>
    <!-- Log Settings -->
    <strap key="baseFilePath" value="/Users/bill/Logs/OC-DataServer">
    <strap key="machineName" value="my-machine">
    <strap key="daysToKeep" value="1">

    <!-- ServerManager Settings -->
    <strap key="cpuMultiplier" value="100">
</straps>

The straps package knows how to read this xml file and provide access to the values via a Singleton based public interface. Since these values only need to be read into memory once a Singleton is a great option for this package.

Here is the package and type information for straps:

package straps

import (
    "encoding/xml"
    "io"
    "os"
    "path/filepath"
    "strconv"
)

.
. Types Removed
.

type straps struct {
    StrapMap map[string]string // The map of strap key value pairs
}

var st straps // A reference to the singleton

I am not going to talk about the aspects of reading the XML document. If you are interested please read this blog post https://www.ardanlabs.com/blog/2013/06/reading-xml-documents-in-go.html.

In the code snippet above you will see the package name (straps), the definition of the private type straps and the private package variable st. The st variable will contain the value for the Singleton.

The scoping rules for Go state that types and functions that start with a capital letter are public and accessible outside of the package. Types and functions that start with a lowercase letter are private and not accessible outside of the package.

I name my variables that are defined within the scope of a function with lowercase letters. Variable names defined outside the scope of a function, such as type members and package variables start with a capital letter. This allows me to look at the code and know instantly where memory for any given variable is being referenced.

Both the straps type and the st variable are private and only accessible from within the package.

Look at the Load function which initializes the Singleton for use:

func MustLoad() {
    // Find the location of the straps.xml file
    strapsFilePath, err := filepath.Abs("straps.xml")

    // Open the straps.xml file
    file, err := os.Open(strapsFilePath)
    if err != nil {
        panic(err.Error())
    }

    defer file.Close()

    // Read the straps file
    xmlStraps, err := readStraps(file)
    if err != nil {
        panic(err.Error())
    }

    // Create a straps object
    st = straps{
        StrapMap: make(map[string]string),
    }

    // Store the key/value pairs for each strap
    for _, strap := range xmlStraps {
        st.StrapMap[strap.Key] = strap.Value
    }
}

The Load function is a public function of the package. Applications can access this function through the package name. You can see how I use names that start with a lowercase letter for local variables. At the bottom of the Load function a straps object is created and the reference is set to the st variable. At this point the Singleton exists and straps is ready to use.

Accessing the straps is done with the public function Strap:

func Strap(key string) string {
    return st.StrapMap[key]
}

The public function Strap uses the Singleton reference to access the shared resource. In this case the map of straps. If the map could change during the lifetime of the application, then a mutex or some other synchronization object would need to be used to protect the map. Luckily the straps never change once they are loaded.

Since the resource being managed by straps is just memory there is no need for an Unload or Close method. If we needed a function to close any resources another public function would have to be created.

If private methods are required in the Singleton package to help organize code, I like to use member functions. Since the type is private I can make the member functions public because they won't be accessible. I also think the member functions help make the code more readable. By looking to see if the function is a member function or not I know if the function is private or part of the public interface.

Here is a example of using a member function:

func SomePublicFunction() {
    .
    st.SomePrivateMemberFunction("key")
    .
}

func (straps *straps) SomePrivateMemberFunction(key string) {
    return straps.StrapMap[key]
    .
}

Since the function is a member function we need to use the st variable to make the function call. From within the member function I use the local variable (straps) and not the st variable. The member function is public but the reference is private so only the package can reference the member function. This is just a convention I established for myself.

Here is a sample program that uses the straps package:

package main

import (
    "ArdanStudios/straps"
)

func main() {
    straps.MustLoad()
    cpu := straps.Strap("cpuMultiplier")
}

In main we don't need to allocate any memory or maintain references. Through the package name we call Load to initialize the Singleton. Then through the package name again we access the public interface, in this case the Strap function.

If you have the same need to managed shared resources through a public interface try using a Singleton.

As always I hope this helps you write better and bug less code.

Trusted by Top Technology Companies

We've built our reputation as educators and bring that mentality to every project. When you partner with us, your team will learn best practices and grow along the way.

30,000+

Engineers Trained

1,000+

Companies Worldwide

14+

Years in Business