Implementing a Basic Mutex in Go
Synchronization is crucial in concurrent programming to prevent race conditions and ensure data integrity. This challenge asks you to implement a simplified version of sync.Mutex in Go, focusing on the core locking and unlocking mechanisms. Understanding how mutexes work is fundamental to writing robust concurrent Go programs.
Problem Description
Your task is to implement a Mutex type that provides Lock() and Unlock() methods. The Lock() method should block until the mutex is available, and the Unlock() method should release the mutex. The implementation should be thread-safe, meaning it should function correctly when accessed concurrently from multiple goroutines. The core of the challenge lies in managing the state of the mutex (locked or unlocked) and ensuring proper blocking/unblocking behavior.
Key Requirements:
Lock()method: Should block the calling goroutine until the mutex is unlocked. Once unlocked, the goroutine should acquire the lock.Unlock()method: Should release the mutex, allowing a waiting goroutine to acquire it. CallingUnlock()when the mutex is not locked should be a no-op (do nothing). CallingUnlock()multiple times should also be a no-op.- Thread-Safety: The
Mutexmust be safe for concurrent access from multiple goroutines.
Expected Behavior:
- Multiple goroutines can attempt to call
Lock()concurrently. Only one should succeed in acquiring the lock. The others should block. - When
Unlock()is called, one of the waiting goroutines should be unblocked and acquire the lock. - The mutex should start in an unlocked state.
Edge Cases to Consider:
- Calling
Unlock()when the mutex is already unlocked. - Calling
Unlock()multiple times. - Multiple goroutines attempting to
Lock()simultaneously. - Goroutines waiting for a long time to acquire the lock.
Examples
Example 1:
Input: Two goroutines, both calling Lock() sequentially.
Output: The first goroutine acquires the lock. The second goroutine blocks until the first goroutine calls Unlock().
Explanation: Demonstrates the blocking behavior of Lock().
Example 2:
Input: One goroutine calls Lock(), then Unlock(). Another goroutine calls Lock() after the first Unlock().
Output: The first goroutine acquires the lock, then releases it. The second goroutine then acquires the lock.
Explanation: Shows the proper sequence of lock acquisition and release.
Example 3:
Input: A goroutine calls Unlock() before it has ever called Lock().
Output: No error or panic. The mutex remains unlocked.
Explanation: Handles the edge case of unlocking an unlocked mutex.
Constraints
- The implementation should use only standard Go library packages (e.g.,
sync,runtime). No external dependencies are allowed. - The
Lock()method should not take more than 100 microseconds to acquire the lock when no other goroutine holds the lock. - The
Unlock()method should complete in less than 10 microseconds. - The
Mutextype should be relatively lightweight (minimal memory overhead).
Notes
- Consider using a simple boolean variable to represent the locked state of the mutex.
- You'll need to use a synchronization primitive (like a channel) to manage the waiting goroutines. Think about how to signal waiting goroutines when the mutex becomes available.
- Focus on the core locking and unlocking logic. Error handling and more advanced features (like timeouts) are not required for this challenge.
- Think carefully about the order of operations within
Lock()andUnlock()to avoid race conditions.