Sling Academy
Home/Golang/Concurrent Utilities: Using `sync` and `sync/atomic` in Go

Concurrent Utilities: Using `sync` and `sync/atomic` in Go

Last updated: November 27, 2024

Go, with its powerful concurrency support, offers a set of utilities that make handling concurrent operations easier and more efficient. In this article, we will explore two important packages: sync and sync/atomic. These packages provide the necessary tools to manage shared variables and synchronize goroutines without the complexity of more traditional mutexes or locks.

The `sync` Package

The sync package is part of Go’s standard library and provides several utilities for concurrent programming:

  • Mutex: A basic lock mechanism for ensuring mutual exclusion.
  • WaitGroup: Useful for waiting for a collection of goroutines to finish executing.
  • Once: Ensures that a particular action is only performed once.
  • Cond: Implements conditional variables.
  • Pool: A thread-safe way to cache and reuse objects.

Using Mutex

Here’s a simple example to demonstrate how a sync.Mutex can be used to protect a shared variable.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex
	var balance int

	wg := sync.WaitGroup{}

	n := 100
	wg.Add(n)

	for i := 0; i < n; i++ {
		go func() {
			mu.Lock()
			balance++
			mu.Unlock()
			wg.Done()
		}()
	}

	wg.Wait()
	fmt.Println("Balance:", balance)
}

In this code, we set up a sync.Mutex instance, mu, which we lock before updating the balance and unlock afterwards. This ensures that only one goroutine can modify balance at a time.

Using WaitGroup

Next, let’s discuss how you can use a sync.WaitGroup to wait for a collection of goroutines to complete.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup

	worker := func(id int) {
		fmt.Printf("Worker %d starting\n", id)
		// Simulate some work
		for i := 0; i < 5; i++ {
			fmt.Printf("Worker %d working\n", id)
		}
		fmt.Printf("Worker %d done\n", id)
		wg.Done()
	}

	workersCount := 3
	wg.Add(workersCount)

	for i := 1; i <= workersCount; i++ {
		go worker(i)
	}

	wg.Wait()
	fmt.Println("All workers done")
}

In this example, the sync.WaitGroup is utilized to track when the set of workers is complete by calling wg.Done() for each worker completion.

The `sync/atomic` Package

The sync/atomic package provides low-level atomic memory primitives that load from and store to memory atomically, ensuring operations are thread-safe. This can be especially useful for updating integers or pointers without locks.

Using Atomic Operations

Consider the following example where we increment a counter atomically.

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var value int64
	wg := sync.WaitGroup{}

	n := 100
	wg.Add(n)
	for i := 0; i < n; i++ {
		go func() {
			atomic.AddInt64(&value, 1)
			wg.Done()
		}()
	}

	wg.Wait()
	fmt.Println("Value:", value)
}

Here, we use atomic.AddInt64(&value, 1) to safely increment the value without explicit locks, providing a significant performance boost in certain scenarios.

Conclusion

The sync and sync/atomic packages in Go are incredibly powerful and provide the essential tools to effectively harness concurrency. Understanding these packages is crucial to making concurrent programming simpler and more efficient in your Go applications.

Next Article: Building Efficient Data Structures with Go's Slices and Maps

Previous Article: Managing Dependencies with `go mod` in Go Projects

Series: Go Utilities and Tools

Golang

Related Articles

You May Also Like

  • How to remove HTML tags in a string in Go
  • How to remove special characters in a string in Go
  • How to remove consecutive whitespace in a string in Go
  • How to count words and characters in a string in Go
  • Relative imports in Go: Tutorial & Examples
  • How to run Python code with Go
  • How to generate slug from title in Go
  • How to create an XML sitemap in Go
  • How to redirect in Go (301, 302, etc)
  • Using Go with MongoDB: CRUD example
  • Auto deploy Go apps with CI/ CD and GitHub Actions
  • Fixing Go error: method redeclared with different receiver type
  • Fixing Go error: copy argument must have slice type
  • Fixing Go error: attempted to use nil slice
  • Fixing Go error: assignment to constant variable
  • Fixing Go error: cannot compare X (type Y) with Z (type W)
  • Fixing Go error: method has pointer receiver, not called with pointer
  • Fixing Go error: assignment mismatch: X variables but Y values
  • Fixing Go error: array index must be non-negative integer constant