Safe Resource Access with Acquire-Release Semantics in Go
Acquire-release semantics are a crucial pattern for managing shared resources safely in concurrent Go programs. This challenge asks you to implement a Resource type that enforces acquire-release semantics using a mutex, ensuring that only one goroutine can access the resource at a time, preventing race conditions and data corruption. This pattern is fundamental for building robust and reliable concurrent applications.
Problem Description
You are tasked with creating a Resource type that encapsulates a shared value and provides methods for acquiring and releasing access to it. The Resource should use a sync.Mutex to protect the underlying value from concurrent access.
The Resource type should have the following methods:
Acquire(): Acquires a lock on the resource. This method should block until the lock is available.Release(): Releases the lock on the resource. This method should be called after the resource is no longer needed.GetValue() int: Returns the current value of the resource. This method must be called only afterAcquire()and beforeRelease().SetValue(value int): Sets the value of the resource. This method must be called only afterAcquire()and beforeRelease().
The goal is to ensure that GetValue() and SetValue() are always called within a locked region, preventing data races. The Acquire() and Release() methods should handle the mutex operations correctly. Failure to acquire or release the lock properly should not cause a panic.
Examples
Example 1:
Input: Initial resource value: 10
Goroutine 1: Acquire(), GetValue(), Release()
Goroutine 2: Acquire(), SetValue(20), Release()
Output:
Goroutine 1: 10
Goroutine 2: 20
Explanation: Goroutine 1 acquires the lock, reads the value (10), and releases the lock. Then, Goroutine 2 acquires the lock, sets the value to 20, and releases the lock. The final value of the resource is 20.
Example 2:
Input: Initial resource value: 5
Goroutine 1: Acquire(), SetValue(15), SetValue(25), Release()
Output:
Resource value: 25
Explanation: Goroutine 1 acquires the lock, sets the value to 15, then to 25, and finally releases the lock. The final value of the resource is 25.
Example 3: (Edge Case - Concurrent Access)
Input: Initial resource value: 0
Goroutine 1: Acquire(), GetValue(), SetValue(1), Release()
Goroutine 2: Acquire(), GetValue(), Release()
Goroutine 3: Acquire(), SetValue(2), Release()
Output:
Goroutine 1: 1
Goroutine 2: 1
Goroutine 3: 2
Explanation: Goroutine 1 acquires the lock, reads the value (0), sets it to 1, and releases. Goroutine 2 acquires the lock, reads the value (1), and releases. Goroutine 3 acquires the lock, sets the value to 2, and releases. The final value of the resource is 2. The mutex ensures that only one goroutine modifies the value at a time.
Constraints
- The resource value is an integer.
- The
Acquire()andRelease()methods should be thread-safe. - The
GetValue()andSetValue()methods should only be called when the resource is locked. While the challenge doesn't require explicit error handling for this, it's important to understand this constraint. - The solution should be efficient and avoid unnecessary overhead.
Notes
- Use the
sync.Mutextype from thesyncpackage to implement the locking mechanism. - Consider the order of operations when acquiring and releasing the lock.
- Think about how to prevent race conditions when multiple goroutines are accessing the resource concurrently.
- The focus is on demonstrating the acquire-release pattern; extensive error handling (e.g., double release) is not required for this challenge, but understanding the potential issues is important.