Hone logo
Hone
Problems

Multi-Producer, Multi-Consumer (MPMC) Queue in Rust

This challenge asks you to implement a thread-safe queue in Rust that supports multiple producers and multiple consumers concurrently. MPMC queues are essential for building concurrent systems where multiple threads need to enqueue data and other threads need to dequeue it, such as in parallel processing pipelines or asynchronous task management. A robust implementation requires careful synchronization to prevent race conditions and ensure data integrity.

Problem Description

You are to implement a generic MPMC queue in Rust. The queue should be able to store any type T. The queue must be thread-safe, allowing multiple producer threads to enqueue data and multiple consumer threads to dequeue data concurrently without data corruption or deadlock.

Key Requirements:

  • enqueue(item: T): Adds an item to the end of the queue. This operation must be thread-safe.
  • dequeue(): Removes and returns an item from the front of the queue. This operation must be thread-safe. If the queue is empty, the dequeue() method should block until an item becomes available.
  • is_empty(): Returns true if the queue is empty, false otherwise. This operation must be thread-safe.
  • capacity(): Returns the maximum number of items the queue can hold.

Expected Behavior:

  • Multiple producer threads should be able to enqueue items concurrently.
  • Multiple consumer threads should be able to dequeue items concurrently.
  • The queue should maintain FIFO (First-In, First-Out) order.
  • The dequeue() method should block if the queue is empty until an item is available.
  • The queue should handle potential panics gracefully.

Edge Cases to Consider:

  • Empty queue: dequeue() should block.
  • Full queue: If the queue has a limited capacity, enqueue() should block when the queue is full until space becomes available.
  • Multiple consumers attempting to dequeue from an empty queue.
  • Multiple producers attempting to enqueue to a full queue.
  • Panics within enqueue or dequeue operations. The queue should remain in a consistent state.

Examples

Example 1:

Input:
Producer 1: Enqueue(1)
Producer 2: Enqueue(2)
Consumer 1: Dequeue()
Consumer 2: Dequeue()
Output:
Consumer 1: 1
Consumer 2: 2
Explanation:  Two producers enqueue items 1 and 2. Two consumers then dequeue them in FIFO order.

Example 2:

Input:
Queue Capacity: 3
Producer 1: Enqueue(1)
Producer 2: Enqueue(2)
Producer 3: Enqueue(3)
Producer 4: Enqueue(4)  // Queue is full, blocks
Consumer 1: Dequeue()
Producer 4: Enqueue(4)  // Queue now has space, enqueues 4
Consumer 2: Dequeue()
Output:
Consumer 1: 1
Consumer 2: 2
Explanation: The queue has a capacity of 3.  The fourth producer blocks until a consumer dequeues.

Example 3: (Edge Case - Empty Queue)

Input:
Queue is empty
Consumer 1: Dequeue()
Producer 1: Enqueue(5)
Consumer 1: Dequeue()
Output:
Consumer 1: 5
Explanation: The consumer blocks until the producer enqueues an item.

Constraints

  • The queue should be generic and able to store any type T.
  • The queue should have a configurable capacity.
  • The implementation should avoid busy-waiting as much as possible. Use appropriate synchronization primitives.
  • The enqueue and dequeue operations should be reasonably efficient (avoid unnecessary copying or allocations).
  • The queue should be able to handle at least 1000 concurrent enqueue and dequeue operations without deadlock or data corruption. (This is a qualitative constraint, not a strict performance benchmark).

Notes

  • Consider using Arc<Mutex<...>> and Condvar from the std::sync module to implement the thread-safe queue.
  • Think carefully about the order of acquiring and releasing locks to prevent deadlocks.
  • The Condvar is crucial for efficiently blocking and waking up threads waiting for items to be added or removed from the queue.
  • Error handling is not explicitly required in this challenge, but consider how your implementation would handle potential errors in a real-world scenario.
  • Focus on correctness and thread safety first, then optimize for performance if time allows.
Loading editor...
rust