codingstuff.io
ExploreTutorialsProblemsCS Subjects
Get Started
ExploreTutorialsProblemsCS Subjects
Get Started
codingstuff.io

Master the art of building software through interactive tutorials, real-world problems, and guided projects.

Pune, Maharashtra, India

codingstuffmail@gmail.com

Product

  • Explore
  • Tutorials
  • Problems
  • CS Subjects

Company

  • About
  • Contact
  • Privacy Policy
  • Terms & Conditions
  • Sitemap

© 2026 codingstuff.io. All rights reserved.

Built with ❤️ for developers everywhere

/
/
All Tutorials
🐹

Go (Golang)

21 / 72 topics
17Concurrency Basics18Goroutines19Channels20Select Statement21Sync Package
Tutorials/Go (Golang)/Sync Package
🐹Go (Golang)

Sync Package

Updated 2026-04-20
3 min read

Sync Package

The sync package in Go provides fundamental synchronization primitives such as mutual exclusion locks, condition variables, and wait groups. These tools are essential for managing concurrent access to shared resources and ensuring that your Go programs run smoothly without data races or deadlocks.

Introduction to Synchronization

Concurrency is a core feature of the Go language, allowing you to write efficient and scalable applications by executing multiple tasks simultaneously. However, when multiple goroutines access shared resources, it's crucial to synchronize their execution to prevent race conditions and ensure data consistency.

The sync package offers several synchronization primitives that help manage concurrent access:

  • Mutexes: Locks for mutual exclusion.
  • RWMutexes: Read-write locks for read-heavy scenarios.
  • WaitGroups: Synchronize the termination of multiple goroutines.
  • Channels: Communicate between goroutines and synchronize execution.

In this tutorial, we'll explore each of these primitives in detail, providing real-world examples and best practices to help you write robust concurrent Go programs.

Mutexes

A Mutex (mutual exclusion lock) is a synchronization primitive that allows only one goroutine to access a shared resource at a time. This prevents race conditions by ensuring exclusive access.

Basic Usage

Here's a simple example of using a Mutex:

package main

import (
    "fmt"
    "sync"
)

var (
    counter int
    mu      sync.Mutex
)

func increment(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock() // Lock the mutex before accessing the shared resource
    counter++
    fmt.Println("Counter:", counter)
    mu.Unlock() // Unlock the mutex after modifying the shared resource
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go increment(&wg)
    }

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

Best Practices

  • Lock and Unlock: Always ensure that the mutex is unlocked after locking. Use defer to handle unlocking automatically.
  • Minimal Locking Scope: Keep the locked section as short as possible to avoid blocking other goroutines unnecessarily.

RWMutexes

An RWMutex (read-write lock) allows multiple readers or a single writer to access a shared resource. This is more efficient than using a regular mutex in scenarios where reads are much more frequent than writes.

Basic Usage

Here's an example of using an RWMutex:

package main

import (
    "fmt"
    "sync"
)

var (
    data     map[string]int
    rwMutex  sync.RWMutex
)

func readData(key string, wg *sync.WaitGroup) {
    defer wg.Done()
    rwMutex.RLock() // Lock for reading
    value := data[key]
    fmt.Println("Read", key, ":", value)
    rwMutex.RUnlock() // Unlock after reading
}

func writeData(key string, value int, wg *sync.WaitGroup) {
    defer wg.Done()
    rwMutex.Lock() // Lock for writing
    data[key] = value
    fmt.Println("Wrote", key, ":", value)
    rwMutex.Unlock() // Unlock after writing
}

func main() {
    var wg sync.WaitGroup

    data = make(map[string]int)

    // Simulate multiple reads and writes
    for i := 0; i < 10; i++ {
        wg.Add(2)
        go readData("key", &wg)
        go writeData("key", i, &wg)
    }

    wg.Wait()
}

Best Practices

  • Prefer RWMutex: Use RWMutex when you have a high ratio of reads to writes.
  • Avoid Deadlocks: Ensure that all readers and writers unlock the mutex correctly.

WaitGroups

A WaitGroup is used to wait for a collection of goroutines to finish executing. It's particularly useful when you need to coordinate the termination of multiple goroutines.

Basic Usage

Here's an example of using a WaitGroup:

package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
    // Simulate work with sleep
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

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

    wg.Wait() // Wait for all workers to finish
    fmt.Println("All workers done")
}

Best Practices

  • Increment and Decrement: Always call Add before starting a goroutine and Done when the goroutine completes.
  • Avoid Deadlocks: Ensure that all goroutines eventually call Done.

Channels

Channels are another fundamental synchronization primitive in Go. They allow goroutines to communicate and synchronize their execution.

Basic Usage

Here's an example of using channels:

package main

import (
    "fmt"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Printf("Worker %d started job %d\n", id, j)
        time.Sleep(time.Second)
        fmt.Printf("Worker %d finished job %d\n", id, j)
        results <- j * 2
    }
}

func main() {
    const numJobs = 5

    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)

    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs) // Close the jobs channel to signal no more jobs

    for a := 1; a <= numJobs; a++ {
        fmt.Println("Result:", <-results)
    }
}

Best Practices

  • Buffered Channels: Use buffered channels when you want to control the flow of data between goroutines.
  • Close Channels: Only close channels that you own. Closing a channel signals that no more values will be sent on it.

Conclusion

The sync package in Go provides essential tools for managing concurrency and synchronizing access to shared resources. By understanding and correctly using mutexes, RWMutexes, WaitGroups, and channels, you can write robust and efficient concurrent programs.

Remember to always follow best practices such as locking and unlocking mutexes correctly, using buffered channels appropriately, and avoiding deadlocks. With these tools at your disposal, you'll be well-equipped to handle complex concurrency scenarios in Go.


PreviousSelect StatementNext Error Handling

Recommended Gear

Select StatementError Handling