Hone logo
Hone
Problems

Achieving Sequential Consistency with Go Goroutines

Sequential consistency is a memory model that guarantees operations appear to execute in a total order, as if they were executed by a single processor. This is a strong consistency model, simplifying reasoning about concurrent programs. This challenge asks you to implement a mechanism in Go that enforces sequential consistency among multiple goroutines accessing and modifying shared data.

Problem Description

You are tasked with creating a SequentialConsistency struct in Go that provides methods for reading and writing shared data while ensuring sequential consistency. The struct should internally manage a queue of operations (reads and writes) and execute them in a strict, sequential order. Each operation should be associated with a unique identifier (an integer).

The SequentialConsistency struct should have the following methods:

  • NewSequentialConsistency(initialValue interface{}): Constructor that initializes the struct with an initial value for the shared data.
  • Read(id int) (interface{}, int): Simulates a read operation. It should enqueue a read operation with the given id. The method should block until the read operation is executed in the sequential order and return the current value of the shared data and the operation ID.
  • Write(id int, value interface{}) int: Simulates a write operation. It should enqueue a write operation with the given id and the provided value. The method should block until the write operation is executed in the sequential order and return the operation ID.
  • GetValue() interface{}: Returns the current value of the shared data. This should not block.

The key requirement is that reads and writes must be executed in a total order, regardless of the order in which they are requested by different goroutines. The operation IDs should be strictly increasing.

Examples

Example 1:

Input:
- Create a SequentialConsistency instance with initial value 0.
- Goroutine 1: Write(1, 10)
- Goroutine 2: Read(2)
- Goroutine 1: Read(3)
- Goroutine 2: Write(4, 20)

Output:
- Goroutine 2: (0, 2)  // Read(2) returns the initial value 0
- Goroutine 1: (10, 3) // Read(3) returns the value after Write(1)
- Goroutine 2: (10, 4) // Write(4) returns 4
- Goroutine 1: (20, 3) // Read(3) returns the value after Write(4)
Explanation:
The operations are executed in the order: Write(1), Read(2), Read(3), Write(4).  The IDs are strictly increasing.

Example 2:

Input:
- Create a SequentialConsistency instance with initial value "hello".
- Goroutine 1: Write(1, "world")
- Goroutine 2: Read(2)
- Goroutine 1: Read(3)

Output:
- Goroutine 2: ("hello", 2)
- Goroutine 1: ("world", 3)
Explanation:
The operations are executed in the order: Read(2), Write(1), Read(3).

Example 3: (Edge Case - Concurrent Reads and Writes)

Input:
- Create a SequentialConsistency instance with initial value 5.
- Goroutine 1: Write(1, 10)
- Goroutine 2: Write(2, 20)
- Goroutine 3: Read(3)
- Goroutine 4: Read(4)

Output (order may vary slightly, but sequential consistency must be maintained):
- Goroutine 3: (5, 3)
- Goroutine 4: (5, 4)
- Goroutine 1: (10, 1)
- Goroutine 2: (20, 2)
Explanation:
Reads should see the latest value after all preceding writes. The order of reads is not critical, but they must see the effects of writes in the correct order.

Constraints

  • The operation ID (id) will be a positive integer.
  • The initial value and the values passed to Write can be of any type (interface{}).
  • The implementation should be reasonably efficient. Blocking should be minimized where possible. Avoid busy-waiting.
  • The operation IDs must be strictly increasing.
  • The Read method must return the value after the write operations that occurred before the read in the sequential order.

Notes

  • Consider using channels and mutexes to synchronize access to the shared data and the operation queue.
  • The core of the solution lies in managing the queue of operations and ensuring they are executed in the correct order.
  • Think about how to handle blocking and unblocking of goroutines waiting for their operations to be executed.
  • The GetValue() method should return the current value without blocking. It should not enqueue any operations.
  • Error handling is not required for this challenge. Focus on the sequential consistency aspect.
Loading editor...
go