Implementing a Read-Write Mutex (sync.RWMutex) in Go
The sync.RWMutex type provides a mechanism for concurrent access to shared data, allowing multiple readers or a single writer. This challenge asks you to implement a simplified version of sync.RWMutex from scratch, focusing on the core locking and unlocking logic. Understanding this implementation will deepen your understanding of concurrency primitives in Go.
Problem Description
You are tasked with creating a RWMutex struct and its associated RLock, RUnlock, Lock, and Unlock methods. The RWMutex should allow multiple goroutines to hold read locks concurrently, but only one goroutine can hold a write lock at any given time. No goroutine can hold a write lock while any read locks are held. The implementation should be thread-safe.
Key Requirements:
RWMutexstruct: This struct should contain the necessary state to manage read and write locks. Consider using channels or other synchronization primitives to achieve thread safety.RLock(): Acquires a read lock. MultipleRLock()calls can be made concurrently. Blocks until no write lock is held.RUnlock(): Releases a read lock. Must be called on a lock that was previously acquired withRLock().Lock(): Acquires a write lock. Blocks until no read or write locks are held.Unlock(): Releases a write lock. Must be called on a lock that was previously acquired withLock().
Expected Behavior:
RLock()calls should return immediately if no write lock is held. If a write lock is held, the goroutine should block until the write lock is released.- Multiple
RLock()calls should be allowed concurrently, and all readers should be able to access the data simultaneously. Lock()calls should block until both read and write locks are released.Unlock()calls should release the appropriate lock (read or write) and unblock any waiting goroutines.- The implementation must be thread-safe, preventing race conditions and ensuring correct locking behavior.
Edge Cases to Consider:
- Calling
RUnlock()orUnlock()on a lock that was not acquired. (While not strictly required for this simplified version, consider how you might handle this in a more robust implementation - panics are acceptable for this challenge). - Multiple goroutines attempting to acquire the write lock simultaneously.
- Goroutines attempting to acquire read locks while a write lock is held.
- Nested locking (e.g., acquiring a read lock while already holding a read lock). This is allowed in the standard
sync.RWMutex, and should be allowed here.
Examples
Example 1:
Input: Two goroutines, one calling Lock() and writing, the other calling RLock() and reading.
Output: The reading goroutine blocks until the writing goroutine unlocks.
Explanation: The write lock prevents concurrent read access.
Example 2:
Input: Three goroutines, all calling RLock() concurrently.
Output: All three goroutines acquire the read lock and proceed.
Explanation: Multiple read locks are allowed.
Example 3:
Input: A goroutine calls Lock(), then RLock(), then Unlock(), then RUnlock().
Output: The operations complete successfully without deadlock.
Explanation: Demonstrates the ability to hold both read and write locks sequentially.
Constraints
- The implementation should be as concise and readable as possible while maintaining correctness.
- The
RWMutexstruct should be defined in a single file. - No external libraries beyond the standard Go library are allowed.
- Performance is not a primary concern for this challenge, but avoid obviously inefficient implementations.
Notes
- Consider using channels to signal waiting goroutines when locks are released.
- Think carefully about the order of operations when acquiring and releasing locks to avoid deadlocks.
- This is a simplified implementation; the standard
sync.RWMutexincludes additional features like fairness and more robust error handling. Focus on the core locking logic. - Error handling (e.g., panicking on invalid unlock calls) is acceptable for this simplified version.