Back to Blog

Thread-Safe Programming with sync - Go Tutorial for Beginners #27

Sandy LaneSandy Lane

Video: Thread-Safe Programming with sync - Go Tutorial for Beginners #27 by Taught by Celeste AI - AI Coding Coach

Watch full page →

Thread-Safe Programming with sync in Go

In Go, the sync package provides powerful primitives to write safe concurrent code by managing access to shared resources. Key tools include Mutex for exclusive locking, RWMutex for read-write locks, Once for one-time initialization, and Pool for efficient object reuse.

Code

package main

import (
  "fmt"
  "sync"
)

// Example using sync.Mutex to protect shared counter
func mutexExample() {
  var mu sync.Mutex
  counter := 0
  var wg sync.WaitGroup

  for i := 0; i < 1000; i++ {
    wg.Add(1)
    go func() {
      defer wg.Done()
      mu.Lock()           // Acquire exclusive lock
      counter++           // Safely increment shared variable
      mu.Unlock()         // Release lock
    }()
  }
  wg.Wait()
  fmt.Println("Counter with Mutex:", counter)
}

// Example using sync.RWMutex for read-heavy workloads
func rwMutexExample() {
  var rw sync.RWMutex
  data := 0
  var wg sync.WaitGroup

  // Writer goroutine
  wg.Add(1)
  go func() {
    defer wg.Done()
    rw.Lock()             // Exclusive lock for writing
    data = 42
    rw.Unlock()
  }()

  // Multiple reader goroutines
  for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(id int) {
      defer wg.Done()
      rw.RLock()          // Shared lock for reading
      fmt.Println("Reader", id, "reads", data)
      rw.RUnlock()
    }(i)
  }
  wg.Wait()
}

// Example using sync.Once for one-time initialization
var once sync.Once
var config string

func loadConfig() {
  fmt.Println("Loading config...")
  config = "Config data loaded"
}

func onceExample() {
  for i := 0; i < 3; i++ {
    go func() {
      once.Do(loadConfig)  // Ensures loadConfig runs only once
      fmt.Println(config)
    }()
  }
}

// Example using sync.Pool for object reuse
func poolExample() {
  var pool = sync.Pool{
    New: func() interface{} {
      return make([]byte, 1024) // Allocate 1KB buffer
    },
  }

  buf := pool.Get().([]byte)  // Get buffer from pool
  fmt.Println("Got buffer of length", len(buf))
  pool.Put(buf)               // Return buffer to pool
}

func main() {
  mutexExample()
  rwMutexExample()
  onceExample()

  // Wait a moment for onceExample goroutines to finish
  // (In real code, use sync.WaitGroup or other sync)
  fmt.Scanln()

  poolExample()
}

Key Points

  • sync.Mutex provides exclusive locking to protect shared data from concurrent access.
  • sync.RWMutex allows multiple readers or one writer, optimizing read-heavy scenarios.
  • sync.Once ensures a function runs only once, useful for safe singleton initialization.
  • sync.Pool enables efficient reuse of temporary objects to reduce GC overhead.
  • Combining these primitives helps write robust and performant concurrent Go programs.