Unlocking Simplicity: Easy Concurrency with the Lock Design Pattern in Go

Photo by PhotoMIX Company: https://www.pexels.com/photo/black-handled-key-on-key-hole-101808/

Introduction

When we build programs that do many things at once, we want to make sure they’re secure. Go is a modern language that’s really good at keeping things safe in memory but still works really fast. One big reason for Go’s safety is its “lock pattern.” This is like a special tool that helps developers make programs that run at the same time but stay safe. Let’s take a closer look at how Go’s lock pattern works and how it can make your projects strong and secure.

Implementation in Go

One of the areas where locks might come in handy, is if you are handing different versions in a version control system. In our example we will build an extremely simplified version control system.

Let’s start with our preparations:

package main

import (
	"fmt"
	"sync"
)

type Version struct {
	Version string
	Content string
}

func NewVersion(version, content string) Version {
	return Version{
		Version: version,
		Content: content,
	}
}

This defines a simple Version struct with a version and some content.

In this example we will go straight to the main function. You could of course wrap this pattern in a struct, if you want, but I want to keep things simple:

func main() {
	list_lock := sync.Mutex{}

	var versions []Version
	var wg sync.WaitGroup

	for counter := 0; counter < 10; counter++ {
		wg.Add(1)
		go func(counter int) {
			defer wg.Done()
			list_lock.Lock()
			defer list_lock.Unlock()
			versions = append(versions, NewVersion(fmt.Sprintf("v0.%d", counter), fmt.Sprintf("content %d", counter)))
		}(counter)
	}

	wg.Wait()
	fmt.Println("Result: ", versions)
}

Some notes:

  1. We define a sync.Mutex to ensure our versions array can only be accessed by one thread at a time.
  2. Next we define our array of Version objects, and a sync.WaitGroup. The WaitGroup is used to make sure all the goroutines have finished before printing the result.
  3. In the loop we do the following
    • We add an item to the WaitGroup
    • We start a goroutine, where we lock our goroutine, we defer unlocking the lock, and signalling the WaitGroup that the thread is done.
    • Because everything is locked, we can safely append a new version to the array.
  4. The next thing is to wait for all the goroutines to finish
  5. Finally we print out the result.

If you try to run it, you won’t see the versions in order, showing that it is really a multi-threaded pattern.

Conclusion

Using the standard library and go-routines, implementing the lock pattern was quite easy. As you can see the intent of the code is clear. The fact that Go has a defer keyword makes some things like unlocking mutex or updating a waitgroup a lot easier.

Leave a Reply

Your email address will not be published. Required fields are marked *