Sling Academy
Home/Golang/Building a Thread-Safe Counter with Go

Building a Thread-Safe Counter with Go

Last updated: November 27, 2024

When working with concurrent programs, handling shared data correctly is crucial to avoid race conditions, which can corrupt that data. One common shared structure is a counter. In this article, we will explore how to create a thread-safe counter in Go using goroutines and the sync package.

Understanding Race Conditions

Before diving into the implementation, it is important to understand what race conditions are. A race condition occurs when two or more goroutines access shared data and they try to change it at the same time. Go provides a tool called race detector to help detect such conditions in your code.

Implementing a Basic Counter

Let's first look at a simple counter without any synchronization:


package main

import (
    "fmt"
)

// Counter is a simple counter
type Counter struct {
    value int
}

func (c *Counter) Increment() {
    c.value++
}

func (c *Counter) Value() int {
    return c.value
}

func main() {
    counter := Counter{}
    counter.Increment()
    fmt.Println(counter.Value()) // 1
}

In this case, the counter is not safe to use with multiple goroutines, as concurrent increments can lead to race conditions.

Making the Counter Thread-Safe

To make the counter thread-safe, we can use a mutex from the sync package which provides mutual exclusion:


package main

import (
    "fmt"
    "sync"
)

// Counter is a thread-safe counter
type Counter struct {
    value int
    mu    sync.Mutex
}

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

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

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

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Println(counter.Value()) // 1000
}

In this code, the sync.Mutex is used to protect the shared value field. The Increment and Value methods lock the mutex, allowing only one goroutine to access the critical section at a time.

Using the sync/atomic Package

Another approach to achieving thread-safety is using the sync/atomic package, which allows atomic operations on variables:


package main

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

// Counter is a thread-safe counter using atomic operations
type Counter struct {
    value int64
}

func (c *Counter) Increment() {
    atomic.AddInt64(&c.value, 1)
}

func (c *Counter) Value() int64 {
    return atomic.LoadInt64(&c.value)
}

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

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Println(counter.Value()) // 1000
}

The sync/atomic package provides atomic operations such as AddInt64 and LoadInt64, ensuring that increments and value retrievals happen atomically without additional locking logic.

Conclusion

Creating a thread-safe counter involves protecting the shared state from concurrent access. Go provides tools like the sync.Mutex for locking mechanisms or the sync/atomic for atomic operations. By leveraging these tools, you can safely increment counters in your concurrent programs.

Next Article: Dynamic Worker Pool Implementation in Go

Previous Article: Deep Dive into Goroutine Lifecycle in Go

Series: Concurrency and Synchronization in Go

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