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:
- We define a
sync.Mutex
to ensure ourversions
array can only be accessed by one thread at a time. - Next we define our array of
Version
objects, and async.WaitGroup
. TheWaitGroup
is used to make sure all the goroutines have finished before printing the result. - 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.
- We add an item to the
- The next thing is to wait for all the goroutines to finish
- 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.