golang

Golang Sync

The Golang Sync package provides synchronization primitives, such as mutex, Waitgroup, Pool, Read and Write mutex, and condition variables.

Let us explore some features provided in this article.

Golang Mutex

The sync.Mutex provides a mutex primitive, which allows for mutual exclusions for shared resources preventing race conditions.

Example usage:

package main
import "sync"
func main() {
    var i = 10
    mutex := &sync.Mutex{}
    mutex.Lock()
    // only one goroutine can access this code at a time
    i++
    mutex.Unlock()
}

Read/Write Mutex

The sync.RWMutex provides a reader/writer mutex. It offers similar methods as a primitive mutex but can allow concurrent reads using RLock and RUnlock methods:

    var i = 10
    mutex := &sync.RWMutex{}
    mutex.Lock()
    // only one goroutine can access this code at a time
    i++
    mutex.Unlock()
    mutex.RLock()
    i++ // concurrent reads
    mutex.RUnlock()

The previous example can allow over one goroutine to read the code. Unlike sync.Mutex, which allows exactly one reader and one writer at a time.

Waitgroups

The sync.Waitgroup is used to offer a blocking mechanism for goroutines. Using a Waitgroup, you can block the execution of a function until all the goroutines have finished executing.

It works by creating a counter which holds the number of goroutines to wait. Once a goroutine is complete, the counter decreases by 1. Once the counter is 0, the Waitgroup unblocks the execution.

To add a value to the Waitgroup counter, we can use the Add() method, which takes an integer value.

To remove a goroutine from the counter after completion, we use the Done() method. For example:

package main
import (
    "fmt"
    "sync"
)
func main() {
    wg := &sync.WaitGroup{}
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(x int) {
            fmt.Printf("Worker: %d running\n", x)
            wg.Done()
        }(i)
    }
    wg.Wait()
}

In the previous example, we increment the value of the Waitgroup counter by 1 through the use of the Add() function.

Once a goroutine is complete, we decrement the counter by 1 using the Done() method. The previous code should return output as:

Worker: 4 running
Worker: 2 running
Worker: 1 running
Worker: 3 running
Worker: 0 running

Once

The sync.Once primitive ensures that a function runs only once. An example is as shown below:

var once sync.Once
RunOnce := func() {
    fmt.Println("Run once")
}
done := make(chan string)
for i := 0; i < 5; i++ {
    go func() {
        once.Do(RunOnce)
        done <- "Hi"
    }()
}
for i := 0; i < 5; i++ {
    <-done
}

Conclusion

This guide covers some basic and fundamental primitives provided by the Go sync package. In addition, synchronization primitives, such as mutex, Waitgroup, Pool, Read and Write mutex, and condition variables, were discussed. We hope you found this article helpful. Check out other Linux Hint articles for more tips and tutorials.

About the author

John Otieno

My name is John and am a fellow geek like you. I am passionate about all things computers from Hardware, Operating systems to Programming. My dream is to share my knowledge with the world and help out fellow geeks. Follow my content by subscribing to LinuxHint mailing list