Simple Message Queue in Go
This challenge asks you to implement a basic message queue in Go. Message queues are fundamental components in distributed systems, enabling asynchronous communication between different parts of an application. Building one from scratch will help you understand concurrency, channels, and synchronization in Go.
Problem Description
You are tasked with creating a message queue that supports Enqueue (adding a message) and Dequeue (removing a message) operations. The queue should be thread-safe, allowing multiple goroutines to enqueue and dequeue messages concurrently. The queue should also handle the case where there are no messages available for dequeueing.
Key Requirements:
- Enqueue(message string): Adds a message to the end of the queue.
- Dequeue() string: Removes and returns the message at the front of the queue. If the queue is empty, it should block until a message is available.
- Thread-Safe: The queue must be safe for concurrent access from multiple goroutines.
- Blocking Dequeue:
Dequeue()should block if the queue is empty, preventing busy-waiting.
Expected Behavior:
- Multiple goroutines can enqueue messages concurrently without data races.
- Multiple goroutines can dequeue messages concurrently.
Dequeue()will block if the queue is empty until a message is enqueued.- Messages are dequeued in the order they were enqueued (FIFO - First-In, First-Out).
Edge Cases to Consider:
- Empty queue:
Dequeue()should block. - Multiple goroutines attempting to dequeue from an empty queue.
- Enqueueing and dequeuing concurrently.
- Potential for panics due to race conditions if not properly synchronized.
Examples
Example 1:
Input:
Enqueue("Message 1")
Enqueue("Message 2")
Dequeue()
Dequeue()
Output:
"Message 1"
"Message 2"
Explanation: Two messages are enqueued, then dequeued in FIFO order.
Example 2:
Input:
Enqueue("Message A")
go func() {
Dequeue()
}()
go func() {
Dequeue()
}()
Output:
"Message A"
"Message A"
Explanation: A single message is enqueued. Two goroutines attempt to dequeue it concurrently. The message is returned to both goroutines (though the exact order of return is not guaranteed, both will receive the same message).
Example 3: (Edge Case - Empty Queue)
Input:
Dequeue()
Enqueue("Message X")
Dequeue()
Output:
(Blocks until "Message X" is enqueued)
"Message X"
Explanation: The first Dequeue() call blocks until a message is enqueued.
Constraints
- The queue should be implemented using Go channels and goroutines.
- The queue should be able to handle at least 1000 concurrent enqueue operations and 1000 concurrent dequeue operations without significant performance degradation. (This is a qualitative constraint, not a strict numerical limit).
- The
messagetype is a string. - No external libraries are allowed (e.g., no using a third-party message queue implementation).
Notes
- Consider using a
sync.Mutexor other synchronization primitives to protect shared data structures. - Channels are a powerful tool for concurrent communication in Go. Think about how you can use them to signal the availability of messages.
- The blocking behavior of
Dequeue()is crucial. Use the channel's blocking receive operation to achieve this. - Focus on correctness and thread safety first. Performance optimization can be considered later.
- Think about how to handle potential errors or panics gracefully. For this challenge, you can assume messages are always valid strings.