Hone logo
Hone
Problems

Implementing Atomic Operations with Go Mutexes

In concurrent programming, ensuring data integrity when multiple goroutines access shared resources is paramount. This challenge focuses on implementing atomic operations using Go's sync.Mutex to protect shared state from race conditions.

Problem Description

Your task is to create a simple counter that can be safely incremented and decremented by multiple goroutines concurrently. You will implement two functions: Increment and Decrement, which will modify a shared integer counter. The key requirement is to ensure that these operations are atomic, meaning that each operation completes entirely without interruption from other goroutines trying to access the same counter.

What needs to be achieved:

  • Implement a counter struct that holds an integer value.
  • Implement Increment and Decrement methods on this struct.
  • Use sync.Mutex to protect the counter's value from concurrent access.

Key requirements:

  • The Increment function should atomically add 1 to the counter.
  • The Decrement function should atomically subtract 1 from the counter.
  • The Value function should return the current value of the counter atomically.

Expected behavior: When multiple goroutines call Increment and Decrement on the same counter instance, the final value should reflect the exact number of increments and decrements performed, without any lost updates due to race conditions.

Important edge cases to consider:

  • What happens if Decrement is called when the counter is already zero or negative? (The problem doesn't strictly forbid negative values, but it's good to be aware.)
  • Ensuring that the lock is always released, even if an unexpected panic were to occur (though this is less likely in this simple scenario, it's a good general practice).

Examples

Example 1:

Input:
Counter starts at 0.
10 goroutines each call Increment 1000 times.

Output:
Counter value: 10000

Explanation: Each of the 10 goroutines performs 1000 increments. With atomic operations, the final value accurately reflects 10 * 1000 = 10000.

Example 2:

Input:
Counter starts at 5.
5 goroutines each call Increment 200 times.
3 goroutines each call Decrement 100 times.

Output:
Counter value: 600

Explanation: Initial value: 5 Total increments: 5 * 200 = 1000 Total decrements: 3 * 100 = 300 Final value: 5 + 1000 - 300 = 705. (Self-correction: The example output was wrong. Correcting to reflect the calculation.) Corrected Output: Counter value: 705

Example 3: (Illustrating concurrent access)

Input:
Counter starts at 0.
Two goroutines:
  - Goroutine A: Calls Increment 5000 times.
  - Goroutine B: Calls Decrement 2500 times.

Output:
Counter value: 2500

Explanation: Goroutine A attempts to increment 5000 times. Goroutine B attempts to decrement 2500 times. Due to atomic operations and mutex protection, the final value will be 5000 - 2500 = 2500, regardless of the interleaving of goroutine execution.

Constraints

  • The counter value will be an int.
  • The number of goroutines and operations can be large, emphasizing the need for efficient and correct concurrency handling.
  • The solution must be implemented in Go.
  • Performance is a consideration, but correctness and atomicity are the primary goals.

Notes

  • Consider using sync.Mutex for locking. Remember to Lock() before accessing the shared resource and Unlock() afterwards.
  • The defer keyword can be very useful for ensuring Unlock() is always called.
  • A sync.WaitGroup can be helpful for coordinating the execution of multiple goroutines and waiting for them to complete before checking the final counter value.
Loading editor...
go