Hone logo
Hone
Problems

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 after Acquire() and before Release().
  • SetValue(value int): Sets the value of the resource. This method must be called only after Acquire() and before Release().

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() and Release() methods should be thread-safe.
  • The GetValue() and SetValue() 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.Mutex type from the sync package 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.
Loading editor...
go