Implementing Write Barriers in Go for Concurrent Map Access
Write barriers are crucial for maintaining data consistency in concurrent programs, particularly when dealing with shared data structures like maps. This challenge focuses on implementing a simple write barrier in Go to prevent race conditions when multiple goroutines concurrently modify a map. The goal is to ensure that readers of the map see a consistent view of the data, even during modifications.
Problem Description
You are tasked with creating a write barrier for a shared map in Go. The write barrier should intercept writes to the map and ensure that any readers of the map are notified of the change before the write is committed. This prevents readers from accessing stale data while the map is being modified. The barrier should be implemented as a middleware function that wraps the standard map write operation.
What needs to be achieved:
- Implement a
WriteBarriertype that encapsulates the shared map. - Provide a
Storemethod on theWriteBarriertype that acts as a write barrier. This method should:- Acquire a lock before modifying the map.
- Notify any waiting readers (using a channel) that a write has occurred.
- Perform the map write operation.
- Release the lock.
- Provide a
Loadmethod on theWriteBarriertype that allows reading from the map. This method should:- Acquire a read lock.
- If a write is in progress (indicated by a signal on the notification channel), wait for the write to complete.
- Release the read lock.
- Return the value associated with the key.
- The
WriteBarriershould use async.Mutexfor mutual exclusion and a buffered channel to signal write events to readers.
Key Requirements:
- Thread Safety: The
StoreandLoadmethods must be thread-safe. - Data Consistency: Readers should always see a consistent view of the map, even during concurrent writes.
- Minimal Blocking: The write barrier should minimize blocking of readers and writers.
Expected Behavior:
- Multiple readers can concurrently access the map without blocking each other.
- A writer will block readers while performing the write operation.
- Once the write operation is complete, all waiting readers will be unblocked and receive a consistent view of the map.
Edge Cases to Consider:
- Multiple writers attempting to write concurrently.
- Readers attempting to read while a writer is actively writing.
- The map being accessed by a large number of concurrent goroutines.
- What happens if a reader is waiting on a write that never completes (e.g., due to a deadlock)? While a full deadlock detection isn't required, consider how your design handles this scenario gracefully (e.g., a timeout).
Examples
Example 1:
Input:
- Shared map: map[string]int{"a": 1, "b": 2}
- Goroutine 1: WriteBarrier.Store("c", 3)
- Goroutine 2: WriteBarrier.Load("a")
Output:
- Goroutine 1 completes the write.
- Goroutine 2 returns 1.
Explanation: Goroutine 2 waits for Goroutine 1 to finish writing before reading "a".
Example 2:
Input:
- Shared map: map[string]int{"a": 1}
- Goroutine 1: WriteBarrier.Store("a", 2)
- Goroutine 2: WriteBarrier.Load("a")
Output:
- Goroutine 1 completes the write.
- Goroutine 2 returns 2.
Explanation: Goroutine 2 waits for Goroutine 1 to finish writing "a" and then returns the updated value.
Example 3: (Edge Case - Concurrent Writes)
Input:
- Shared map: map[string]int{"a": 1}
- Goroutine 1: WriteBarrier.Store("a", 2)
- Goroutine 2: WriteBarrier.Store("b", 3)
Output:
- Goroutine 1 completes the write to "a".
- Goroutine 2 completes the write to "b".
Explanation: The writes are serialized due to the mutex, ensuring consistency.
Constraints
- The map will only contain string keys and integer values.
- The number of concurrent readers and writers will be limited to 100.
- The write barrier should introduce minimal overhead to the map operations. Avoid unnecessary copying or complex data structures.
- The buffered channel should have a capacity of 1.
Notes
- Consider using a
sync.Mutexto protect the map from concurrent access. - A buffered channel can be used to signal write events to waiting readers.
- The
Loadmethod should block until a write operation is complete. - Think about how to handle the case where a reader is waiting indefinitely for a write that never happens. While a full deadlock detection isn't required, consider a timeout mechanism or a way to signal an error to the reader.
- Focus on correctness and thread safety first, then optimize for performance if necessary.