taken from this site

Concurrency is one of the standout features of Golang, enabling efficient and scalable parallel execution of tasks. To leverage the full potential of Go’s concurrency model, understanding the sync package is crucial. This article will explore the sync package, focusing on its key primitives like Mutex, RWMutex, and WaitGroups (not including Cond) with detailed explanations and practical examples.

taken from this site

Introduction to the sync Package

The sync package provides basic synchronization primitives to manage concurrent execution in Go. These tools help ensure that only one goroutine accesses shared resources at a time, preventing race conditions and ensuring data integrity.

Mutex: Mutual Exclusion Locks

A Mutex (mutual exclusion lock) is the simplest and most common synchronization primitive in the sync package. It allows only one goroutine to access a critical code section at a time.

package main

import (
“fmt”
“sync”
)

// Counter struct with a mutex
type Counter struct {
mu sync.Mutex
value int
}

// Increment increments the counter value safely
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}

// Value returns the current value of the counter
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}

func main() {
var wg sync.WaitGroup
counter := Counter{}

// Number of goroutines to spawn
numGoroutines := 10

// Number of increments per goroutine
incrementsPerGoroutine := 100

wg.Add(numGoroutines)

// Spawn multiple goroutines
for i := 0; i < numGoroutines; i++ {
go func() {
defer wg.Done()
for j := 0; j < incrementsPerGoroutine; j++ {
counter.Increment()
}
}()
}

// Wait for all goroutines to finish
wg.Wait()

// Print the final value of the counter
fmt.Printf(“Final Counter Value: %dn”, counter.Value())
}

Explanation

Mutex Initialization:
We start by defining a Counter struct that contains a sync.Mutex field named mu and an integer field value.Increment Method:
The Increment method of the Counter struct demonstrates how to safely increment the value field using the mutex.
– It locks the mutex (c.mu.Lock()), ensuring exclusive access to value.
– After incrementing the value, defer c.mu.Unlock() unlocks the mutex to allow other goroutines to access Increment.Main Function:
In the main function, we create a Counter instance named counter.
– We use a sync.WaitGroup named wg to coordinate multiple goroutines. Here, we spawn 10 goroutines, each calling counter.Increment() in a loop.Goroutines and Incrementing:
Each goroutine increments the Counter value 100 times in a loop (for j := 0; j < 100; j++).
– The Increment method ensures that each increment operation is atomic and thread-safe by locking and unlocking the mutex.Output Explanation:
After all goroutines finish executing (wg.Wait() waits for all goroutines to complete), we print the final value of the counter (counter.Value()).
– Since each goroutine increments the counter 100 times, and there are 10 goroutines, the expected final value of the counter should be 1000.
– The output will show Final Counter Value: 1000, indicating that all increments were successfully synchronized and totaled correctly.

RWMutex: Read-Write Locks

An RWMutex (read-write mutex) allows multiple readers or a single writer, but not both simultaneously. This is useful for scenarios where reads are more frequent than writes.

package main

import (
“fmt”
“sync”
“time”
)

type SafeMap struct {
mu sync.RWMutex
m map[string]string
}

func (s *SafeMap) Get(key string) (string, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
val, ok := s.m[key] return val, ok
}

func (s *SafeMap) Set(key, value string) {
s.mu.Lock()
defer s.mu.Unlock()
s.m[key] = value
}

func main() {
safeMap := &SafeMap{m: make(map[string]string)}

// Setting a key-value pair
safeMap.Set(“key1”, “value1”)

// Concurrently reading from the map
var wg sync.WaitGroup
wg.Add(2)

go func() {
defer wg.Done()
val, ok := safeMap.Get(“key1”)
if ok {
fmt.Println(“Goroutine 1: Value retrieved ->”, val)
} else {
fmt.Println(“Goroutine 1: Key not found”)
}
}()

go func() {
defer wg.Done()
val, ok := safeMap.Get(“key2”)
if ok {
fmt.Println(“Goroutine 2: Value retrieved ->”, val)
} else {
fmt.Println(“Goroutine 2: Key not found”)
}
}()

wg.Wait()
}

Explanation

SafeMap Struct and Methods:
SafeMap struct contains a sync.RWMutex named mu and a map m to store key-value pairs.
– Methods Get and Set use mu.RLock() and mu.Lock() respectively to safely read and write to the map.Setting a Key-Value Pair:
safeMap.Set(“key1”, “value1”) adds a key-value pair to the map m.Concurrent Read Operations:
Two goroutines are launched concurrently (wg.Add(2)), each performing a read operation using safeMap.Get().Output Analysis:
– Goroutine 1 Output:
Since “key1” exists in the map (Set was called before), “value1” is retrieved successfully. Output: Goroutine 1: Value retrieved -> value1.
Goroutine 2 Output: “key2” does not exist in the map (Set was not called for “key2”), so the retrieval operation returns false for ok. Output: Goroutine 2: Key not found.Conclusion:
The sync.RWMutex (mu) allows multiple goroutines to read from the map concurrently (Get method) while ensuring exclusive access during writes (Set method).
– This example demonstrates how sync.RWMutex supports efficient concurrent read operations and sequential write operations, ensuring data consistency and preventing race conditions.

WaitGroup: Waiting for Goroutines to Finish

A WaitGroup waits for a collection of goroutines to finish. It provides a simple way to synchronize the completion of multiple goroutines.

package main

import (
“fmt”
“sync”
“time”
)

func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Decrements the counter when the goroutine completes
fmt.Printf(“Worker %d startingn”, id)
time.Sleep(time.Second) // Simulate work with a sleep of 1 second
fmt.Printf(“Worker %d donen”, id)
}

func main() {
var wg sync.WaitGroup // Create a WaitGroup

for i := 1; i <= 5; i++ {
wg.Add(1) // Increment the WaitGroup counter
go worker(i, &wg) // Launch goroutines with the WaitGroup
}

wg.Wait() // Wait for all goroutines to finish

fmt.Println(“All workers have completed their tasks”)
}

Explanation

WaitGroup Initialization:
var wg sync.WaitGroup initializes a WaitGroup variable wg, which manages a counter for goroutines.Goroutine Execution:
In the for loop, wg.Add(1) increments the WaitGroup counter before launching each goroutine (go worker(i, &wg)).Worker Function:
worker function simulates a task with time.Sleep(time.Second) and prints messages indicating the start and completion of work for each goroutine (Worker %d starting and Worker %d done).wg.Done():
Inside worker, defer wg.Done() decrements the WaitGroup counter when the function completes execution. This signals that the goroutine has finished its work.Main Function Wait:
wg.Wait() blocks the main goroutine until all goroutines launched from the loop have called wg.Done() to decrement the counter. This ensures that main waits for all workers to complete before printing All workers have completed their tasks.Output Explanation:
– Each worker (goroutine) starts execution sequentially as indicated by Worker %d starting.
– Due to the time.Sleep(time.Second) in worker, each goroutine takes 1 second to complete its work.
– After completion, each worker prints Worker %d done.
– Since the WaitGroup in main waits for all goroutines to call wg.Done(), the final print statement All workers have completed their tasks is only executed after all five workers have finished their respective tasks.

This example demonstrates how sync.WaitGroup can synchronize the execution of multiple goroutines and coordinate their completion in a structured manner, ensuring all tasks are completed before proceeding further in the main program.

Conclusion

The sync package in Golang offers powerful primitives for managing concurrency. By understanding and using Mutex, RWMutex, and WaitGroups effectively, you can write robust and efficient concurrent programs. Mastering these tools is essential for any Go developer looking to build high-performance applications.

This is one part of concurrency in Go. Next article, we will focus on Channels.
Feel free to connect with me on LinkedIn!

Mastering Advanced Concurrency in Golang: A Deep Dive into the sync Package was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.

​ Level Up Coding – Medium

about Infinite Loop Digital

We support businesses by identifying requirements and helping clients integrate AI seamlessly into their operations.

Gartner
Gartner Digital Workplace Summit Generative Al

GenAI sessions:

  • 4 Use Cases for Generative AI and ChatGPT in the Digital Workplace
  • How the Power of Generative AI Will Transform Knowledge Management
  • The Perils and Promises of Microsoft 365 Copilot
  • How to Be the Generative AI Champion Your CIO and Organization Need
  • How to Shift Organizational Culture Today to Embrace Generative AI Tomorrow
  • Mitigate the Risks of Generative AI by Enhancing Your Information Governance
  • Cultivate Essential Skills for Collaborating With Artificial Intelligence
  • Ask the Expert: Microsoft 365 Copilot
  • Generative AI Across Digital Workplace Markets
10 – 11 June 2024

London, U.K.