» Quick Introduction to Go » 3. Advanced » 3.4 Sync

Sync

Package sync provides basic synchronization primitives such as mutual exclusion locks. Other than the Once and WaitGroup types, most are intended for use by low-level library routines. Higher-level synchronization is better done via channels and communication.

Once

The sync.Once type ensures that a function is only executed once, regardless of how many goroutines call it. This can be useful for scenarios where you want to perform some initialization or setup.

package main

import (
	"fmt"
	"sync"
  "time"
)

var (
	initialized bool
	initOnce    sync.Once
)

func initialize() {
	fmt.Println("Initializing...")
	// Perform initialization tasks here
	initialized = true
}

func performTask() {
	// The function passed to sync.Once.Do will only be executed once,
	// even if multiple goroutines call it.
	initOnce.Do(initialize)

	// Perform the actual task here
	fmt.Println("Performing the task...")
}

func main() {
	// Run performTask in multiple goroutines
	for i := 0; i < 5; i++ {
		go performTask()
	}

	fmt.Println("Waiting for tasks to complete...")
	time.Sleep(2 * time.Second)
}
// =>
// Waiting for tasks to complete...
// Initializing...
// Performing the task...
// Performing the task...
// Performing the task...
// Performing the task...
// Performing the task...

WaitGroups

The sync.WaitGroup is used to wait for a collection of goroutines to finish their execution.

package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // Decrement the counter when the goroutine completes

	fmt.Printf("Worker %d started\n", id)
	time.Sleep(time.Second) // Simulating some work
	fmt.Printf("Worker %d completed\n", id)
}

func main() {
	var wg sync.WaitGroup

	numWorkers := 3

	for i := 1; i <= numWorkers; i++ {
		wg.Add(1) // Increment the counter before starting a new goroutine
		go worker(i, &wg)
	}

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

	fmt.Println("All workers have completed.")
}
// =>
// Worker 3 started
// Worker 1 started
// Worker 2 started
// Worker 3 completed
// Worker 2 completed
// Worker 1 completed
// All workers have completed.

Atomic Counters

The sync/atomic package provides atomic operations for basic types, including atomic counters. Atomic operations are essential for managing shared state in concurrent programs without the need for locks.

package main

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

func incrementCounter(counter *int64, wg *sync.WaitGroup) {
	defer wg.Done()

	for i := 0; i < 10000; i++ {
		// Atomically increment the counter
		atomic.AddInt64(counter, 1)
	}

	time.Sleep(5 * time.Millisecond)
}

func main() {
	var counter int64

	var wg sync.WaitGroup

	numGoroutines := 5

	for i := 0; i < numGoroutines; i++ {
		wg.Add(1)
		go incrementCounter(&counter, &wg)
	}

	wg.Wait()

	fmt.Println("Final Counter Value:", atomic.LoadInt64(&counter))
}
// => Final Counter Value: 50000

Mutexes

A mutex (short for mutual exclusion) is a synchronization primitive that protects shared data from being accessed concurrently by multiple goroutines.

package main

import (
	"fmt"
	"sync"
)

type Counter struct {
	value int
	mu    sync.Mutex
}

func (c *Counter) Increment() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.value++
}

func (c *Counter) GetValue() int {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.value
}

func main() {
	var wg sync.WaitGroup

	counter := Counter{}

	numGoroutines := 5

	for i := 0; i < numGoroutines; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()

			for j := 0; j < 10000; j++ {
				counter.Increment()
			}
		}()
	}

	wg.Wait()

	fmt.Println("Final Counter Value:", counter.GetValue())
}
// => Final Counter Value: 50000

Mutexes are suitable for protecting complex shared resources, critical sections, or scenarios where fine-grained control over locking is needed. While atomic counters are suitable for simple scenarios where the shared resource is a single counter or flag.

Code Challenge

Implement a concurrent counter that can be incremented and decremented by multiple goroutines safely using mutexes.

Loading...
> code result goes here
Prev
Next